Updated all libraries while updating PyQode to fix an auto-complete bug.

This commit is contained in:
Jeremy Wright 2015-04-07 13:31:39 -04:00
parent fea739562c
commit 6f2b0a4c53
109 changed files with 5640 additions and 5938 deletions

View File

@ -2271,3 +2271,27 @@ class Workplane(CQ):
#combine everything
return self.union(boxes)
def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360, centered=(True, True, True), combine=True):
# Convert the direction tuple to a vector, if needed
if isinstance(direct, tuple):
direct = Vector(direct)
def _makesphere(pnt):
(xp, yp, zp) = pnt.toTuple()
if centered[0]:
xp = xp - radius
if centered[1]:
yp = yp - radius
if centered[2]:
zp = zp - radius
return Solid.makeSphere(radius, Vector(xp, yp, zp), direct, angle1, angle2, angle3)
spheres = self.eachpoint(_makesphere, True)
# If we don't need to combine everything, just return the created spheres
if not combine:
return spheres
else:
return self.union(spheres)

View File

@ -638,13 +638,13 @@ class Solid(Shape):
FreeCADPart.makeWedge(xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt, dir))
@classmethod
def makeSphere(cls, radius, pnt=None, angleDegrees1=None, angleDegrees2=None, angleDegrees3=None):
def makeSphere(cls, radius, pnt=None, dir=None, angleDegrees1=None, angleDegrees2=None, angleDegrees3=None):
"""
'makeSphere(radius,[pnt, dir, angle1,angle2,angle3]) --
Make a sphere with a giv
en radius\nBy default pnt=Vector(0,0,0), dir=Vector(0,0,1), angle1=0, angle2=90 and angle3=360'
"""
return Solid(FreeCADPart.makeSphere(radius, pnt, angleDegrees1, angleDegrees2, angleDegrees3))
return Shape.cast(FreeCADPart.makeSphere(radius, pnt.wrapped, dir.wrapped, angleDegrees1, angleDegrees2, angleDegrees3))
@classmethod
def extrudeLinearWithRotation(cls, outerWire, innerWires, vecCenter, vecNormal, angleDegrees):

View File

@ -1 +0,0 @@
__version__ = '1.4.1'

View File

@ -1,153 +0,0 @@
"""frosted/api.py.
Defines the api for the command-line frosted utility
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
"""
import os
import re
import sys
import tokenize
from io import StringIO
from token import N_TOKENS
from pies.overrides import *
import _ast
from frosted import reporter as modReporter
from frosted import checker, settings
from frosted.messages import FileSkipped, PythonSyntaxError
__all__ = ['check', 'check_path', 'check_recursive', 'iter_source_code']
_re_noqa = re.compile(r'((frosted)[:=]\s*noqa)|(#\s*noqa)', re.I)
def _noqa_lines(codeString):
line_nums = []
g = tokenize.generate_tokens(StringIO(str(codeString)).readline) # tokenize the string
for toknum, tokval, begins, _, _ in g:
lineno = begins[0]
# not sure what N_TOKENS really means, but in testing, that was what comments were
# tokenized as
if toknum == N_TOKENS:
if _re_noqa.search(tokval):
line_nums.append(lineno)
return line_nums
def _should_skip(filename, skip):
if filename in skip:
return True
position = os.path.split(filename)
while position[1]:
if position[1] in skip:
return True
position = os.path.split(position[0])
def check(codeString, filename, reporter=modReporter.Default, settings_path=None, **setting_overrides):
"""Check the Python source given by codeString for unfrosted flakes."""
if not settings_path and filename:
settings_path = os.path.dirname(os.path.abspath(filename))
settings_path = settings_path or os.getcwd()
active_settings = settings.from_path(settings_path).copy()
for key, value in itemsview(setting_overrides):
access_key = key.replace('not_', '').lower()
if type(active_settings.get(access_key)) in (list, tuple):
if key.startswith('not_'):
active_settings[access_key] = list(set(active_settings[access_key]).difference(value))
else:
active_settings[access_key] = list(set(active_settings[access_key]).union(value))
else:
active_settings[key] = value
active_settings.update(setting_overrides)
if _should_skip(filename, active_settings.get('skip', [])):
if active_settings.get('directly_being_checked', None) == 1:
reporter.flake(FileSkipped(filename))
return 1
elif active_settings.get('verbose', False):
ignore = active_settings.get('ignore_frosted_errors', [])
if(not "W200" in ignore and not "W201" in ignore):
reporter.flake(FileSkipped(filename, None, verbose=active_settings.get('verbose')))
return 0
# First, compile into an AST and handle syntax errors.
try:
tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
except SyntaxError:
value = sys.exc_info()[1]
msg = value.args[0]
(lineno, offset, text) = value.lineno, value.offset, value.text
# If there's an encoding problem with the file, the text is None.
if text is None:
# Avoid using msg, since for the only known case, it contains a
# bogus message that claims the encoding the file declared was
# unknown.
reporter.unexpected_error(filename, 'problem decoding source')
else:
reporter.flake(PythonSyntaxError(filename, msg, lineno, offset, text,
verbose=active_settings.get('verbose')))
return 1
except Exception:
reporter.unexpected_error(filename, 'problem decoding source')
return 1
# Okay, it's syntactically valid. Now check it.
w = checker.Checker(tree, filename, None, ignore_lines=_noqa_lines(codeString), **active_settings)
w.messages.sort(key=lambda m: m.lineno)
for warning in w.messages:
reporter.flake(warning)
return len(w.messages)
def check_path(filename, reporter=modReporter.Default, settings_path=None, **setting_overrides):
"""Check the given path, printing out any warnings detected."""
try:
with open(filename, 'U') as f:
codestr = f.read() + '\n'
except UnicodeError:
reporter.unexpected_error(filename, 'problem decoding source')
return 1
except IOError:
msg = sys.exc_info()[1]
reporter.unexpected_error(filename, msg.args[1])
return 1
return check(codestr, filename, reporter, settings_path, **setting_overrides)
def iter_source_code(paths):
"""Iterate over all Python source files defined in paths."""
for path in paths:
if os.path.isdir(path):
for dirpath, dirnames, filenames in os.walk(path):
for filename in filenames:
if filename.endswith('.py'):
yield os.path.join(dirpath, filename)
else:
yield path
def check_recursive(paths, reporter=modReporter.Default, settings_path=None, **setting_overrides):
"""Recursively check all source files defined in paths."""
warnings = 0
for source_path in iter_source_code(paths):
warnings += check_path(source_path, reporter, settings_path=None, **setting_overrides)
return warnings

View File

@ -1,905 +0,0 @@
"""frosted/checker.py.
The core functionality of frosted lives here. Implements the core checking capability models Bindings and Scopes
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import builtins
import doctest
import itertools
import os
import sys
from pies import ast
from pies.overrides import *
from frosted import messages
PY34_GTE = sys.version_info >= (3, 4)
FROSTED_BUILTINS = set(dir(builtins) + ['__file__', '__builtins__', '__debug__', '__name__', 'WindowsError',
'__import__'] +
os.environ.get('PYFLAKES_BUILTINS', '').split(','))
def node_name(node):
"""
Convenience function: Returns node.id, or node.name, or None
"""
return hasattr(node, 'id') and node.id or hasattr(node, 'name') and node.name
class Binding(object):
"""Represents the binding of a value to a name.
The checker uses this to keep track of which names have been bound and which names have not. See Assignment for a
special type of binding that is checked with stricter rules.
"""
__slots__ = ('name', 'source', 'used')
def __init__(self, name, source):
self.name = name
self.source = source
self.used = False
def __str__(self):
return self.name
def __repr__(self):
return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
self.name,
self.source.lineno,
id(self))
class Importation(Binding):
"""A binding created by an import statement."""
__slots__ = ('fullName', )
def __init__(self, name, source):
self.fullName = name
name = name.split('.')[0]
super(Importation, self).__init__(name, source)
class Argument(Binding):
"""Represents binding a name as an argument."""
__slots__ = ()
class Definition(Binding):
"""A binding that defines a function or a class."""
__slots__ = ()
class Assignment(Binding):
"""Represents binding a name with an explicit assignment.
The checker will raise warnings for any Assignment that isn't used. Also, the checker does not consider assignments
in tuple/list unpacking to be Assignments, rather it treats them as simple Bindings.
"""
__slots__ = ()
class FunctionDefinition(Definition):
__slots__ = ('signature', )
def __init__(self, name, source):
super(FunctionDefinition, self).__init__(name, source)
self.signature = FunctionSignature(source)
class ClassDefinition(Definition):
__slots__ = ()
class ExportBinding(Binding):
"""A binding created by an __all__ assignment. If the names in the list
can be determined statically, they will be treated as names for export and
additional checking applied to them.
The only __all__ assignment that can be recognized is one which takes
the value of a literal list containing literal strings. For example:
__all__ = ["foo", "bar"]
Names which are imported and not otherwise used but appear in the value of
__all__ will not have an unused import warning reported for them.
"""
__slots__ = ()
def names(self):
"""Return a list of the names referenced by this binding."""
names = []
if isinstance(self.source, ast.List):
for node in self.source.elts:
if isinstance(node, ast.Str):
names.append(node.s)
return names
class Scope(dict):
importStarred = False # set to True when import * is found
def __repr__(self):
scope_cls = self.__class__.__name__
return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self))
class ClassScope(Scope):
pass
class FunctionScope(Scope):
"""Represents the name scope for a function."""
uses_locals = False
always_used = set(['__tracebackhide__', '__traceback_info__', '__traceback_supplement__'])
def __init__(self):
Scope.__init__(self)
self.globals = self.always_used.copy()
def unusedAssignments(self):
"""Return a generator for the assignments which have not been used."""
for name, binding in self.items():
if (not binding.used and name not in self.globals
and not self.uses_locals
and isinstance(binding, Assignment)):
yield name, binding
class GeneratorScope(Scope):
pass
class ModuleScope(Scope):
pass
class FunctionSignature(object):
__slots__ = ('decorated', 'argument_names', 'default_count', 'kw_only_argument_names', 'default_count',
'kw_only_argument_names', 'kw_only_default_count', 'has_var_arg', 'has_kw_arg')
def __init__(self, node):
self.decorated = bool(any(node.decorator_list))
self.argument_names = ast.argument_names(node)
self.default_count = len(node.args.defaults)
self.kw_only_argument_names = ast.kw_only_argument_names(node)
self.kw_only_default_count = ast.kw_only_default_count(node)
self.has_var_arg = node.args.vararg is not None
self.has_kw_arg = node.args.kwarg is not None
def min_argument_count(self):
return len(self.argument_names) - self.default_count
def maxArgumentCount(self):
return len(self.argument_names)
def checkCall(self, call_node, reporter, name):
if self.decorated:
return
filledSlots = set()
filledKwOnlySlots = set()
for item, arg in enumerate(call_node.args):
if item >= len(self.argument_names):
if not self.has_var_arg:
return reporter.report(messages.TooManyArguments, call_node, name, self.maxArgumentCount())
break
filledSlots.add(item)
for kw in call_node.keywords:
slots = None
try:
argIndex = self.argument_names.index(kw.arg)
slots = filledSlots
except ValueError:
try:
argIndex = self.kw_only_argument_names.index(kw.arg)
slots = filledKwOnlySlots
except ValueError:
if self.has_kw_arg:
continue
else:
return reporter.report(messages.UnexpectedArgument, call_node, name, kw.arg)
if argIndex in slots:
return reporter.report(messages.MultipleValuesForArgument, call_node, name, kw.arg)
slots.add(argIndex)
filledSlots.update(range(len(self.argument_names) - self.default_count, len(self.argument_names)))
filledKwOnlySlots.update(range(len(self.kw_only_argument_names) - self.kw_only_default_count,
len(self.kw_only_argument_names)))
if (len(filledSlots) < len(self.argument_names) and not call_node.starargs and not call_node.kwargs):
return reporter.report(messages.TooFewArguments, call_node, name, self.min_argument_count())
if (len(filledKwOnlySlots) < len(self.kw_only_argument_names) and not call_node.kwargs):
missing_arguments = [repr(arg) for i, arg in enumerate(self.kw_only_argument_names)
if i not in filledKwOnlySlots]
return reporter.report(messages.NeedKwOnlyArgument, call_node, name, ', '.join(missing_arguments))
class Checker(object):
"""The core of frosted, checks the cleanliness and sanity of Python code."""
node_depth = 0
offset = None
trace_tree = False
frosted_builtins = FROSTED_BUILTINS
def __init__(self, tree, filename='(none)', builtins=None, ignore_lines=(), **settings):
self.settings = settings
self.ignore_errors = settings.get('ignore_frosted_errors', [])
self.ignore_lines = ignore_lines
file_specifc_ignores = settings.get('ignore_frosted_errors_for_' + (os.path.basename(filename) or ""), None)
if file_specifc_ignores:
self.ignore_errors += file_specifc_ignores
self._node_handlers = {}
self._deferred_functions = []
self._deferred_assignments = []
self.dead_scopes = []
self.messages = []
self.filename = filename
if builtins:
self.frosted_builtins = self.frosted_builtins.union(builtins)
self.scope_stack = [ModuleScope()]
self.except_handlers = [()]
self.futures_allowed = True
self.root = tree
self.handle_children(tree)
self.run_deferred(self._deferred_functions)
self._deferred_functions = None
self.run_deferred(self._deferred_assignments)
self._deferred_assignments = None
del self.scope_stack[1:]
self.pop_scope()
self.check_dead_scopes()
def defer_function(self, callable):
"""Schedule a function handler to be called just before completion.
This is used for handling function bodies, which must be deferred because code later in the file might modify
the global scope. When 'callable' is called, the scope at the time this is called will be restored, however it
will contain any new bindings added to it.
"""
self._deferred_functions.append((callable, self.scope_stack[:], self.offset))
def defer_assignment(self, callable):
"""Schedule an assignment handler to be called just after deferred
function handlers."""
self._deferred_assignments.append((callable, self.scope_stack[:], self.offset))
def run_deferred(self, deferred):
"""Run the callables in deferred using their associated scope stack."""
for handler, scope, offset in deferred:
self.scope_stack = scope
self.offset = offset
handler()
@property
def scope(self):
return self.scope_stack[-1]
def pop_scope(self):
self.dead_scopes.append(self.scope_stack.pop())
def check_dead_scopes(self):
"""Look at scopes which have been fully examined and report names in
them which were imported but unused."""
for scope in self.dead_scopes:
export = isinstance(scope.get('__all__'), ExportBinding)
if export:
all = scope['__all__'].names()
# Look for possible mistakes in the export list
if not scope.importStarred and os.path.basename(self.filename) != '__init__.py':
undefined = set(all) - set(scope)
for name in undefined:
self.report(messages.UndefinedExport, scope['__all__'].source, name)
else:
all = []
# Look for imported names that aren't used without checking imports in namespace definition
for importation in scope.values():
if isinstance(importation, Importation) and not importation.used and importation.name not in all:
self.report(messages.UnusedImport, importation.source, importation.name)
def push_scope(self, scope_class=FunctionScope):
self.scope_stack.append(scope_class())
def push_function_scope(self): # XXX Deprecated
self.push_scope(FunctionScope)
def push_class_scope(self): # XXX Deprecated
self.push_scope(ClassScope)
def report(self, message_class, *args, **kwargs):
error_code = message_class.error_code
if(not error_code[:2] + "00" in self.ignore_errors and not error_code in self.ignore_errors and not
str(message_class.error_number) in self.ignore_errors):
kwargs['verbose'] = self.settings.get('verbose')
message = message_class(self.filename, *args, **kwargs)
if message.lineno not in self.ignore_lines:
self.messages.append(message)
def has_parent(self, node, kind):
while hasattr(node, 'parent'):
node = node.parent
if isinstance(node, kind):
return True
def get_common_ancestor(self, lnode, rnode, stop=None):
stop = stop or self.root
if lnode is rnode:
return lnode
if stop in (lnode, rnode):
return stop
if not hasattr(lnode, 'parent') or not hasattr(rnode, 'parent'):
return
if (lnode.level > rnode.level):
return self.get_common_ancestor(lnode.parent, rnode, stop)
if (rnode.level > lnode.level):
return self.get_common_ancestor(lnode, rnode.parent, stop)
return self.get_common_ancestor(lnode.parent, rnode.parent, stop)
def descendant_of(self, node, ancestors, stop=None):
for ancestor in ancestors:
if self.get_common_ancestor(node, ancestor, stop) not in (stop, None):
return True
return False
def on_fork(self, parent, lnode, rnode, items):
return (self.descendant_of(lnode, items, parent) ^ self.descendant_of(rnode, items, parent))
def different_forks(self, lnode, rnode):
"""True, if lnode and rnode are located on different forks of
IF/TRY."""
ancestor = self.get_common_ancestor(lnode, rnode)
if isinstance(ancestor, ast.If):
for fork in (ancestor.body, ancestor.orelse):
if self.on_fork(ancestor, lnode, rnode, fork):
return True
elif isinstance(ancestor, ast.Try):
body = ancestor.body + ancestor.orelse
for fork in [body] + [[hdl] for hdl in ancestor.handlers]:
if self.on_fork(ancestor, lnode, rnode, fork):
return True
elif isinstance(ancestor, ast.TryFinally):
if self.on_fork(ancestor, lnode, rnode, ancestor.body):
return True
return False
def add_binding(self, node, value, report_redef=True):
"""Called when a binding is altered.
- `node` is the statement responsible for the change
- `value` is the optional new value, a Binding instance, associated
with the binding; if None, the binding is deleted if it exists.
- if `report_redef` is True (default), rebinding while unused will be
reported.
"""
redefinedWhileUnused = False
if not isinstance(self.scope, ClassScope):
for scope in self.scope_stack[::-1]:
existing = scope.get(value.name)
if (isinstance(existing, Importation)
and not existing.used
and (not isinstance(value, Importation) or
value.fullName == existing.fullName)
and report_redef
and not self.different_forks(node, existing.source)):
redefinedWhileUnused = True
self.report(messages.RedefinedWhileUnused,
node, value.name, existing.source)
existing = self.scope.get(value.name)
if not redefinedWhileUnused and self.has_parent(value.source, ast.ListComp):
if (existing and report_redef
and not self.has_parent(existing.source, (ast.For, ast.ListComp))
and not self.different_forks(node, existing.source)):
self.report(messages.RedefinedInListComp,
node, value.name, existing.source)
if (isinstance(existing, Definition)
and not existing.used
and not self.different_forks(node, existing.source)):
self.report(messages.RedefinedWhileUnused,
node, value.name, existing.source)
else:
self.scope[value.name] = value
def get_node_handler(self, node_class):
try:
return self._node_handlers[node_class]
except KeyError:
nodeType = str(node_class.__name__).upper()
self._node_handlers[node_class] = handler = getattr(self, nodeType)
return handler
def iter_visible_scopes(self):
outerScopes = itertools.islice(self.scope_stack, len(self.scope_stack) - 1)
scopes = [scope for scope in outerScopes
if isinstance(scope, (FunctionScope, ModuleScope))]
if (isinstance(self.scope, GeneratorScope)
and scopes[-1] != self.scope_stack[-2]):
scopes.append(self.scope_stack[-2])
scopes.append(self.scope_stack[-1])
return iter(reversed(scopes))
def handle_node_load(self, node):
name = node_name(node)
if not name:
return
importStarred = False
for scope in self.iter_visible_scopes():
importStarred = importStarred or scope.importStarred
try:
scope[name].used = (self.scope, node)
except KeyError:
pass
else:
return
# look in the built-ins
if importStarred or name in self.frosted_builtins:
return
if name == '__path__' and os.path.basename(self.filename) == '__init__.py':
# the special name __path__ is valid only in packages
return
# protected with a NameError handler?
if 'NameError' not in self.except_handlers[-1]:
self.report(messages.UndefinedName, node, name)
def handle_node_store(self, node):
name = node_name(node)
if not name:
return
# if the name hasn't already been defined in the current scope
if isinstance(self.scope, FunctionScope) and name not in self.scope:
# for each function or module scope above us
for scope in self.scope_stack[:-1]:
if not isinstance(scope, (FunctionScope, ModuleScope)):
continue
# if the name was defined in that scope, and the name has
# been accessed already in the current scope, and hasn't
# been declared global
used = name in scope and scope[name].used
if used and used[0] is self.scope and name not in self.scope.globals:
# then it's probably a mistake
self.report(messages.UndefinedLocal,
scope[name].used[1], name, scope[name].source)
break
parent = getattr(node, 'parent', None)
if isinstance(parent, (ast.For, ast.comprehension, ast.Tuple, ast.List)):
binding = Binding(name, node)
elif (parent is not None and name == '__all__' and
isinstance(self.scope, ModuleScope)):
binding = ExportBinding(name, parent.value)
else:
binding = Assignment(name, node)
if name in self.scope:
binding.used = self.scope[name].used
self.add_binding(node, binding)
def handle_node_delete(self, node):
name = node_name(node)
if not name:
return
if isinstance(self.scope, FunctionScope) and name in self.scope.globals:
self.scope.globals.remove(name)
else:
try:
del self.scope[name]
except KeyError:
self.report(messages.UndefinedName, node, name)
def handle_children(self, tree):
for node in ast.iter_child_nodes(tree):
self.handleNode(node, tree)
def is_docstring(self, node):
"""Determine if the given node is a docstring, as long as it is at the
correct place in the node tree."""
return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and
isinstance(node.value, ast.Str))
def docstring(self, node):
if isinstance(node, ast.Expr):
node = node.value
if not isinstance(node, ast.Str):
return (None, None)
# Computed incorrectly if the docstring has backslash
doctest_lineno = node.lineno - node.s.count('\n') - 1
return (node.s, doctest_lineno)
def handleNode(self, node, parent):
if node is None:
return
if self.offset and getattr(node, 'lineno', None) is not None:
node.lineno += self.offset[0]
node.col_offset += self.offset[1]
if self.trace_tree:
print(' ' * self.node_depth + node.__class__.__name__)
if self.futures_allowed and not (isinstance(node, ast.ImportFrom) or
self.is_docstring(node)):
self.futures_allowed = False
self.node_depth += 1
node.level = self.node_depth
node.parent = parent
try:
handler = self.get_node_handler(node.__class__)
handler(node)
finally:
self.node_depth -= 1
if self.trace_tree:
print(' ' * self.node_depth + 'end ' + node.__class__.__name__)
_get_doctest_examples = doctest.DocTestParser().get_examples
def handle_doctests(self, node):
try:
docstring, node_lineno = self.docstring(node.body[0])
if not docstring:
return
examples = self._get_doctest_examples(docstring)
except (ValueError, IndexError):
# e.g. line 6 of the docstring for <string> has inconsistent
# leading whitespace: ...
return
node_offset = self.offset or (0, 0)
self.push_scope()
for example in examples:
try:
tree = compile(example.source, "<doctest>", "exec", ast.PyCF_ONLY_AST)
except SyntaxError:
e = sys.exc_info()[1]
position = (node_lineno + example.lineno + e.lineno,
example.indent + 4 + (e.offset or 0))
self.report(messages.DoctestSyntaxError, node, position)
else:
self.offset = (node_offset[0] + node_lineno + example.lineno,
node_offset[1] + example.indent + 4)
self.handle_children(tree)
self.offset = node_offset
self.pop_scope()
def find_return_with_argument(self, node):
"""Finds and returns a return statment that has an argument.
Note that we should use node.returns in Python 3, but this method is never called in Python 3 so we don't bother
checking.
"""
for item in node.body:
if isinstance(item, ast.Return) and item.value:
return item
elif not isinstance(item, ast.FunctionDef) and hasattr(item, 'body'):
return_with_argument = self.find_return_with_argument(item)
if return_with_argument:
return return_with_argument
def is_generator(self, node):
"""Checks whether a function is a generator by looking for a yield
statement or expression."""
if not isinstance(node.body, list):
# lambdas can not be generators
return False
for item in node.body:
if isinstance(item, (ast.Assign, ast.Expr)):
if isinstance(item.value, ast.Yield):
return True
elif not isinstance(item, ast.FunctionDef) and hasattr(item, 'body'):
if self.is_generator(item):
return True
return False
def ignore(self, node):
pass
# "stmt" type nodes
RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = TRYFINALLY = ASSERT = EXEC = EXPR = handle_children
CONTINUE = BREAK = PASS = ignore
# "expr" type nodes
BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = YIELDFROM = COMPARE = REPR = ATTRIBUTE = SUBSCRIPT = \
LIST = TUPLE = STARRED = NAMECONSTANT = handle_children
NUM = STR = BYTES = ELLIPSIS = ignore
# "slice" type nodes
SLICE = EXTSLICE = INDEX = handle_children
# expression contexts are node instances too, though being constants
LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore
# same for operators
AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = BITOR = BITXOR = BITAND = FLOORDIV = INVERT = \
NOT = UADD = USUB = EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
# additional node types
COMPREHENSION = KEYWORD = handle_children
def GLOBAL(self, node):
"""Keep track of globals declarations."""
if isinstance(self.scope, FunctionScope):
self.scope.globals.update(node.names)
NONLOCAL = GLOBAL
def LISTCOMP(self, node):
# handle generators before element
for gen in node.generators:
self.handleNode(gen, node)
self.handleNode(node.elt, node)
def GENERATOREXP(self, node):
self.push_scope(GeneratorScope)
# handle generators before element
for gen in node.generators:
self.handleNode(gen, node)
self.handleNode(node.elt, node)
self.pop_scope()
SETCOMP = GENERATOREXP
def DICTCOMP(self, node):
self.push_scope(GeneratorScope)
for gen in node.generators:
self.handleNode(gen, node)
self.handleNode(node.key, node)
self.handleNode(node.value, node)
self.pop_scope()
def FOR(self, node):
"""Process bindings for loop variables."""
vars = []
def collectLoopVars(n):
if isinstance(n, ast.Name):
vars.append(n.id)
elif isinstance(n, ast.expr_context):
return
else:
for c in ast.iter_child_nodes(n):
collectLoopVars(c)
collectLoopVars(node.target)
for varn in vars:
if (isinstance(self.scope.get(varn), Importation)
# unused ones will get an unused import warning
and self.scope[varn].used):
self.report(messages.ImportShadowedByLoopVar,
node, varn, self.scope[varn].source)
self.handle_children(node)
def NAME(self, node):
"""Handle occurrence of Name (which can be a load/store/delete
access.)"""
# Locate the name in locals / function / globals scopes.
if isinstance(node.ctx, (ast.Load, ast.AugLoad)):
self.handle_node_load(node)
if (node.id == 'locals' and isinstance(self.scope, FunctionScope)
and isinstance(node.parent, ast.Call)):
# we are doing locals() call in current scope
self.scope.uses_locals = True
elif isinstance(node.ctx, (ast.Store, ast.AugStore)):
self.handle_node_store(node)
elif isinstance(node.ctx, ast.Del):
self.handle_node_delete(node)
else:
# must be a Param context -- this only happens for names in function
# arguments, but these aren't dispatched through here
raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
def CALL(self, node):
f = node.func
if isinstance(f, ast.Name):
for scope in self.iter_visible_scopes():
definition = scope.get(f.id)
if definition:
if isinstance(definition, FunctionDefinition):
definition.signature.checkCall(node, self, f.id)
break
self.handle_children(node)
def FUNCTIONDEF(self, node):
for deco in node.decorator_list:
self.handleNode(deco, node)
self.add_binding(node, FunctionDefinition(node.name, node))
self.LAMBDA(node)
if self.settings.get('run_doctests', False):
self.defer_function(lambda: self.handle_doctests(node))
def LAMBDA(self, node):
args = []
annotations = []
if PY2:
def addArgs(arglist):
for arg in arglist:
if isinstance(arg, ast.Tuple):
addArgs(arg.elts)
else:
if arg.id in args:
self.report(messages.DuplicateArgument,
node, arg.id)
args.append(arg.id)
addArgs(node.args.args)
defaults = node.args.defaults
else:
for arg in node.args.args + node.args.kwonlyargs:
annotations.append(arg.annotation)
args.append(arg.arg)
defaults = node.args.defaults + node.args.kw_defaults
# Only for Python3 FunctionDefs
is_py3_func = hasattr(node, 'returns')
for arg_name in ('vararg', 'kwarg'):
wildcard = getattr(node.args, arg_name)
if not wildcard:
continue
args.append(getattr(wildcard, 'arg', wildcard))
if is_py3_func:
if PY34_GTE:
annotations.append(wildcard.annotation)
else:
argannotation = arg_name + 'annotation'
annotations.append(getattr(node.args, argannotation))
if is_py3_func:
annotations.append(node.returns)
if PY3:
if len(set(args)) < len(args):
for (idx, arg) in enumerate(args):
if arg in args[:idx]:
self.report(messages.DuplicateArgument, node, arg)
for child in annotations + defaults:
if child:
self.handleNode(child, node)
def runFunction():
self.push_scope()
for name in args:
self.add_binding(node, Argument(name, node), report_redef=False)
if isinstance(node.body, list):
# case for FunctionDefs
for stmt in node.body:
self.handleNode(stmt, node)
else:
# case for Lambdas
self.handleNode(node.body, node)
def checkUnusedAssignments():
"""Check to see if any assignments have not been used."""
for name, binding in self.scope.unusedAssignments():
self.report(messages.UnusedVariable, binding.source, name)
self.defer_assignment(checkUnusedAssignments)
if PY2:
def checkReturnWithArgumentInsideGenerator():
"""Check to see if there are any return statements with
arguments but the function is a generator."""
if self.is_generator(node):
stmt = self.find_return_with_argument(node)
if stmt is not None:
self.report(messages.ReturnWithArgsInsideGenerator, stmt)
self.defer_assignment(checkReturnWithArgumentInsideGenerator)
self.pop_scope()
self.defer_function(runFunction)
def CLASSDEF(self, node):
"""Check names used in a class definition, including its decorators,
base classes, and the body of its definition.
Additionally, add its name to the current scope.
"""
for deco in node.decorator_list:
self.handleNode(deco, node)
for baseNode in node.bases:
self.handleNode(baseNode, node)
if not PY2:
for keywordNode in node.keywords:
self.handleNode(keywordNode, node)
self.push_scope(ClassScope)
if self.settings.get('run_doctests', False):
self.defer_function(lambda: self.handle_doctests(node))
for stmt in node.body:
self.handleNode(stmt, node)
self.pop_scope()
self.add_binding(node, ClassDefinition(node.name, node))
def ASSIGN(self, node):
self.handleNode(node.value, node)
for target in node.targets:
self.handleNode(target, node)
def AUGASSIGN(self, node):
self.handle_node_load(node.target)
self.handleNode(node.value, node)
self.handleNode(node.target, node)
def IMPORT(self, node):
for alias in node.names:
name = alias.asname or alias.name
importation = Importation(name, node)
self.add_binding(node, importation)
def IMPORTFROM(self, node):
if node.module == '__future__':
if not self.futures_allowed:
self.report(messages.LateFutureImport,
node, [n.name for n in node.names])
else:
self.futures_allowed = False
for alias in node.names:
if alias.name == '*':
self.scope.importStarred = True
self.report(messages.ImportStarUsed, node, node.module)
continue
name = alias.asname or alias.name
importation = Importation(name, node)
if node.module == '__future__':
importation.used = (self.scope, node)
self.add_binding(node, importation)
def TRY(self, node):
handler_names = []
# List the exception handlers
for handler in node.handlers:
if isinstance(handler.type, ast.Tuple):
for exc_type in handler.type.elts:
handler_names.append(node_name(exc_type))
elif handler.type:
handler_names.append(node_name(handler.type))
# Memorize the except handlers and process the body
self.except_handlers.append(handler_names)
for child in node.body:
self.handleNode(child, node)
self.except_handlers.pop()
# Process the other nodes: "except:", "else:", "finally:"
for child in ast.iter_child_nodes(node):
if child not in node.body:
self.handleNode(child, node)
TRYEXCEPT = TRY
def EXCEPTHANDLER(self, node):
# 3.x: in addition to handling children, we must handle the name of
# the exception, which is not a Name node, but a simple string.
if node.type is None:
self.report(messages.BareExcept, node)
if isinstance(node.name, str):
self.handle_node_store(node)
self.handle_children(node)

View File

@ -1,51 +0,0 @@
#!/usr/bin/env python
""" Implementation of the command-line frosted tool.
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import argparse
import sys
from pies.overrides import *
from frosted import __version__
from frosted.api import check, check_path, check_recursive
def main():
warnings = 0
parser = argparse.ArgumentParser(description='Quickly check the correctness of your Python scripts.')
parser.add_argument('files', nargs='+', help='One file or a list of Python source files to check the syntax of.')
parser.add_argument('-r', '--recursive', dest='recursive', action='store_true',
help='Recursively look for Python files to check')
parser.add_argument('-s', '--skip', help='Files that frosted should skip over.', dest='skip', action='append')
parser.add_argument('-d', '--with-doctests', help='Run frosted against doctests', dest='run_doctests',
action='store_true')
parser.add_argument('-i', '--ignore', help='Specify error codes that should be ignored.',
dest='ignore_frosted_errors', action='append')
parser.add_argument('-di', '--dont-ignore', help='Specify error codes that should not be ignored in any case.',
dest='not_ignore_frosted_errors', action='append')
parser.add_argument('-vb', '--verbose', help='Explicitly separate each section of data when displaying errors.',
dest='verbose', action='store_true')
parser.add_argument('-v', '--version', action='version', version='frosted {0}'.format(__version__))
arguments = dict((key, value) for (key, value) in itemsview(vars(parser.parse_args())) if value)
file_names = arguments.pop('files', [])
if file_names == ['-']:
check(sys.stdin.read(), '<stdin>', **arguments)
elif arguments.get('recursive'):
warnings = check_recursive(file_names, **arguments)
else:
warnings = 0
for file_path in file_names:
try:
warnings += check_path(file_path, directly_being_checked=len(file_names), **arguments)
except IOError as e:
print("WARNING: Unable to parse file {0} due to {1}".format(file_name, e))
raise SystemExit(warnings > 0)
if __name__ == "__main__":
main()

View File

@ -1,116 +0,0 @@
"""frosted/reporter.py.
Defines the error messages that frosted can output
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import re
from collections import namedtuple
from pies.overrides import *
BY_CODE = {}
_ERROR_INDEX = 100
AbstractMessageType = namedtuple('AbstractMessageType', ('error_code', 'name', 'template',
'keyword', 'error_number'))
class MessageType(AbstractMessageType):
class Message(namedtuple('Message', ('message', 'type', 'lineno', 'col'))):
def __str__(self):
return self.message
def __new__(cls, error_code, name, template, keyword='{0!s}'):
global _ERROR_INDEX
new_instance = AbstractMessageType.__new__(cls, error_code, name, template,
keyword, _ERROR_INDEX)
_ERROR_INDEX += 1
BY_CODE[error_code] = new_instance
return new_instance
def __call__(self, filename, loc=None, *kargs, **kwargs):
values = {'filename': filename, 'lineno': 0, 'col': 0}
if loc:
values['lineno'] = loc.lineno
values['col'] = getattr(loc, 'col_offset', 0)
values.update(kwargs)
message = self.template.format(*kargs, **values)
if kwargs.get('verbose', False):
keyword = self.keyword.format(*kargs, **values)
return self.Message('{0}:{1}:{2}:{3}:{4}:{5}'.format(filename, values['lineno'], values['col'],
self.error_code, keyword, message),
self, values['lineno'], values['col'])
return self.Message('{0}:{1}: {2}'.format(filename, values['lineno'], message),
self, values['lineno'], values['col'])
class OffsetMessageType(MessageType):
def __call__(self, filename, loc, position=None, *kargs, **kwargs):
if position:
kwargs.update({'lineno': position[0], 'col': position[1]})
return MessageType.__call__(self, filename, loc, *kargs, **kwargs)
class SyntaxErrorType(MessageType):
def __call__(self, filename, msg, lineno, offset, text, *kargs, **kwargs):
kwargs['lineno'] = lineno
line = text.splitlines()[-1]
msg += "\n" + str(line)
if offset is not None:
offset = offset - (len(text) - len(line))
kwargs['col'] = offset
msg += "\n" + re.sub(r'\S',' ', line[:offset]) + "^"
return MessageType.__call__(self, filename, None, msg, *kargs, **kwargs)
Message = MessageType('I101', 'Generic', '{0}', '')
UnusedImport = MessageType('E101', 'UnusedImport', '{0} imported but unused')
RedefinedWhileUnused = MessageType('E301', 'RedefinedWhileUnused',
'redefinition of {0!r} from line {1.lineno!r}')
RedefinedInListComp = MessageType('E302', 'RedefinedInListComp',
'list comprehension redefines {0!r} from line {1.lineno!r}')
ImportShadowedByLoopVar = MessageType('E102', 'ImportShadowedByLoopVar',
'import {0!r} from line {1.lineno!r} shadowed by loop variable')
ImportStarUsed = MessageType('E103', 'ImportStarUsed',
"'from {0!s} import *' used; unable to detect undefined names", '*')
UndefinedName = MessageType('E303', 'UndefinedName', "undefined name {0!r}")
DoctestSyntaxError = OffsetMessageType('E401', 'DoctestSyntaxError', "syntax error in doctest", '')
UndefinedExport = MessageType('E304', 'UndefinedExport', "undefined name {0!r} in __all__")
UndefinedLocal = MessageType('E305', 'UndefinedLocal',
'local variable {0!r} (defined in enclosing scope on line {1.lineno!r}) referenced before assignment')
DuplicateArgument = MessageType('E206', 'DuplicateArgument', "duplicate argument {0!r} in function definition")
Redefined = MessageType('E306', 'Redefined', "redefinition of {0!r} from line {1.lineno!r}")
LateFutureImport = MessageType('E207', 'LateFutureImport', "future import(s) {0!r} after other statements")
UnusedVariable = MessageType('E307', 'UnusedVariable', "local variable {0!r} is assigned to but never used")
MultipleValuesForArgument = MessageType('E201', 'MultipleValuesForArgument',
"{0!s}() got multiple values for argument {1!r}")
TooFewArguments = MessageType('E202', 'TooFewArguments', "{0!s}() takes at least {1:d} argument(s)")
TooManyArguments = MessageType('E203', 'TooManyArguments', "{0!s}() takes at most {1:d} argument(s)")
UnexpectedArgument = MessageType('E204', 'UnexpectedArgument', "{0!s}() got unexpected keyword argument: {1!r}")
NeedKwOnlyArgument = MessageType('E205', 'NeedKwOnlyArgument', "{0!s}() needs kw-only argument(s): {1!s}")
ReturnWithArgsInsideGenerator = MessageType('E208', 'ReturnWithArgsInsideGenerator',
"'return' with argument inside generator", 'return')
BareExcept = MessageType('W101', 'BareExcept', "bare except used: this is dangerous and should be avoided", 'except')
FileSkipped = MessageType('W201', 'FileSkipped', "Skipped because of the current configuration", 'skipped')
PythonSyntaxError = SyntaxErrorType('E402', 'PythonSyntaxError', "{0!s}", "")

View File

@ -1,39 +0,0 @@
"""frosted/reporter.py.
Defines how errors found by frosted should be displayed to the user
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import sys
from collections import namedtuple
from pies.overrides import *
class Reporter(namedtuple('Reporter', ('stdout', 'stderr'))):
"""Formats the results of frosted checks and then presents them to the user."""
def unexpected_error(self, filename, msg):
"""Output an unexpected_error specific to the provided filename."""
self.stderr.write("%s: %s\n" % (filename, msg))
def flake(self, message):
"""Print an error message to stdout."""
self.stdout.write(str(message))
self.stdout.write('\n')
Default = Reporter(sys.stdout, sys.stderr)

View File

@ -1,118 +0,0 @@
"""frosted/settings.py.
Defines how the default settings for frosted should be loaded
(First from the default setting dictionary at the top of the file, then overridden by any settings
in ~/.frosted.conf if there are any)
Copyright (C) 2013 Timothy Edmund Crosley
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import os
from pies.functools import lru_cache
from pies.overrides import *
try:
import configparser
except ImportError:
import ConfigParser as configparser
MAX_CONFIG_SEARCH_DEPTH = 25 # The number of parent directories frosted will look for a config file within
# Note that none of these lists must be complete as they are simply fallbacks for when included auto-detection fails.
default = {'skip': [],
'ignore_frosted_errors': ['W201'],
'ignore_frosted_errors_for__init__.py': ['E101', 'E103'],
'verbose': False,
'run_doctests': False}
@lru_cache()
def from_path(path):
computed_settings = default.copy()
_update_settings_with_config(path, '.editorconfig', '~/.editorconfig', ('*', '*.py', '**.py'), computed_settings)
_update_settings_with_config(path, '.frosted.cfg', '~/.frosted.cfg', ('settings', ), computed_settings)
_update_settings_with_config(path, 'setup.cfg', None, ('frosted', ), computed_settings)
return computed_settings
def _update_settings_with_config(path, name, default, sections, computed_settings):
editor_config_file = default and os.path.expanduser(default)
tries = 0
current_directory = path
while current_directory and tries < MAX_CONFIG_SEARCH_DEPTH:
potential_path = os.path.join(current_directory, native_str(name))
if os.path.exists(potential_path):
editor_config_file = potential_path
break
current_directory = os.path.split(current_directory)[0]
tries += 1
if editor_config_file and os.path.exists(editor_config_file):
_update_with_config_file(computed_settings, editor_config_file, sections)
def _update_with_config_file(computed_settings, file_path, sections):
settings = _get_config_data(file_path, sections)
if not settings:
return
for key, value in settings.items():
access_key = key.replace('not_', '').lower()
if key.startswith('ignore_frosted_errors_for'):
existing_value_type = list
else:
existing_value_type = type(default.get(access_key, ''))
if existing_value_type in (list, tuple):
existing_data = set(computed_settings.get(access_key, default.get(access_key)) or ())
if key.startswith('not_'):
computed_settings[access_key] = list(existing_data.difference(value.split(",")))
else:
computed_settings[access_key] = list(existing_data.union(value.split(",")))
elif existing_value_type == bool and value.lower().strip() == "false":
computed_settings[access_key] = False
else:
computed_settings[access_key] = existing_value_type(value)
@lru_cache()
def _get_config_data(file_path, sections):
with open(file_path) as config_file:
if file_path.endswith(".editorconfig"):
line = "\n"
last_position = config_file.tell()
while line:
line = config_file.readline()
if "[" in line:
config_file.seek(last_position)
break
last_position = config_file.tell()
config = configparser.SafeConfigParser()
config.readfp(config_file)
settings = dict()
for section in sections:
if config.has_section(section):
settings.update(dict(config.items(section)))
return settings
return None

View File

@ -1,3 +0,0 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from pies.overrides import *

View File

@ -1,302 +0,0 @@
"""frosted/test/test_api.py.
Tests all major functionality of the Frosted API
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import os
import sys
import tempfile
from io import StringIO
import pytest
from pies.overrides import *
from frosted.api import check_path, check_recursive
from frosted.messages import PythonSyntaxError, UnusedImport
from frosted.reporter import Reporter
from .utils import LoggingReporter, Node
def test_syntax_error():
"""syntax_error reports that there was a syntax error in the source file.
It reports to the error stream and includes the filename, line number, error message, actual line of source and a
caret pointing to where the error is
"""
err = StringIO()
reporter = Reporter(err, err)
reporter.flake(PythonSyntaxError('foo.py', 'a problem', 3, 7, 'bad line of source', verbose=True))
assert ("foo.py:3:7:E402::a problem\n"
"bad line of source\n"
" ^\n") == err.getvalue()
def test_syntax_errorNoOffset():
"""syntax_error doesn't include a caret pointing to the error if offset is passed as None."""
err = StringIO()
reporter = Reporter(err, err)
reporter.flake(PythonSyntaxError('foo.py', 'a problem', 3, None, 'bad line of source', verbose=True))
assert ("foo.py:3:0:E402::a problem\n"
"bad line of source\n") == err.getvalue()
def test_multiLineSyntaxError():
""" If there's a multi-line syntax error, then we only report the last line.
The offset is adjusted so that it is relative to the start of the last line
"""
err = StringIO()
lines = ['bad line of source', 'more bad lines of source']
reporter = Reporter(err, err)
reporter.flake(PythonSyntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7, '\n'.join(lines), verbose=True))
assert ("foo.py:3:6:E402::a problem\n" +
lines[-1] + "\n" +
" ^\n") == err.getvalue()
def test_unexpected_error():
"""unexpected_error reports an error processing a source file."""
err = StringIO()
reporter = Reporter(None, err)
reporter.unexpected_error('source.py', 'error message')
assert 'source.py: error message\n' == err.getvalue()
def test_flake():
"""flake reports a code warning from Frosted.
It is exactly the str() of a frosted.messages.Message
"""
out = StringIO()
reporter = Reporter(out, None)
message = UnusedImport('foo.py', Node(42), 'bar')
reporter.flake(message)
assert out.getvalue() == "%s\n" % (message,)
def make_temp_file(content):
"""Make a temporary file containing C{content} and return a path to it."""
_, fpath = tempfile.mkstemp()
if not hasattr(content, 'decode'):
content = content.encode('ascii')
fd = open(fpath, 'wb')
fd.write(content)
fd.close()
return fpath
def assert_contains_output(path, flakeList):
"""Assert that provided causes at minimal the errors provided in the error list."""
out = StringIO()
count = check_path(path, Reporter(out, out), verbose=True)
out_string = out.getvalue()
assert len(flakeList) >= count
for flake in flakeList:
assert flake in out_string
def get_errors(path):
"""Get any warnings or errors reported by frosted for the file at path."""
log = []
reporter = LoggingReporter(log)
count = check_path(path, reporter)
return count, log
def test_missingTrailingNewline():
"""Source which doesn't end with a newline shouldn't cause any exception to
be raised nor an error indicator to be returned by check.
"""
fName = make_temp_file("def foo():\n\tpass\n\t")
assert_contains_output(fName, [])
def test_check_pathNonExisting():
"""check_path handles non-existing files"""
count, errors = get_errors('extremo')
assert count == 1
assert errors == [('unexpected_error', 'extremo', 'No such file or directory')]
def test_multilineSyntaxError():
"""Source which includes a syntax error which results in the raised SyntaxError.
text containing multiple lines of source are reported with only
the last line of that source.
"""
source = """\
def foo():
'''
def bar():
pass
def baz():
'''quux'''
"""
# Sanity check - SyntaxError.text should be multiple lines, if it
# isn't, something this test was unprepared for has happened.
def evaluate(source):
exec(source)
try:
evaluate(source)
except SyntaxError:
e = sys.exc_info()[1]
assert e.text.count('\n') > 1
else:
assert False
sourcePath = make_temp_file(source)
assert_contains_output(
sourcePath,
["""\
%s:8:10:E402::invalid syntax
'''quux'''
^
""" % (sourcePath,)])
def test_eofSyntaxError():
"""The error reported for source files which end prematurely causing a
syntax error reflects the cause for the syntax error.
"""
sourcePath = make_temp_file("def foo(")
assert_contains_output(sourcePath, ["""\
%s:1:8:E402::unexpected EOF while parsing
def foo(
^
""" % (sourcePath,)])
def test_nonDefaultFollowsDefaultSyntaxError():
""" Source which has a non-default argument following a default argument
should include the line number of the syntax error
However these exceptions do not include an offset
"""
source = """\
def foo(bar=baz, bax):
pass
"""
sourcePath = make_temp_file(source)
last_line = ' ^\n' if sys.version_info >= (3, 2) else ''
column = '7:' if sys.version_info >= (3, 2) else '0:'
assert_contains_output(sourcePath, ["""\
%s:1:%sE402::non-default argument follows default argument
def foo(bar=baz, bax):
%s""" % (sourcePath, column, last_line)])
def test_nonKeywordAfterKeywordSyntaxError():
"""Source which has a non-keyword argument after a keyword argument
should include the line number of the syntax error
However these exceptions do not include an offset
"""
source = """\
foo(bar=baz, bax)
"""
sourcePath = make_temp_file(source)
last_line = ' ^\n' if sys.version_info >= (3, 2) else ''
column = '12:' if sys.version_info >= (3, 2) else '0:'
assert_contains_output(
sourcePath,
["""\
%s:1:%sE402::non-keyword arg after keyword arg
foo(bar=baz, bax)
%s""" % (sourcePath, column, last_line)])
def test_invalidEscape():
"""The invalid escape syntax raises ValueError in Python 2."""
sourcePath = make_temp_file(r"foo = '\xyz'")
if PY2:
decoding_error = "%s: problem decoding source\n" % (sourcePath,)
else:
decoding_error = "(unicode error) 'unicodeescape' codec can't decode bytes"
assert_contains_output(sourcePath, (decoding_error, ))
def test_permissionDenied():
"""If the source file is not readable, this is reported on standard error."""
sourcePath = make_temp_file('')
os.chmod(sourcePath, 0)
count, errors = get_errors(sourcePath)
assert count == 1
assert errors == [('unexpected_error', sourcePath, "Permission denied")]
def test_frostedWarning():
"""If the source file has a frosted warning, this is reported as a 'flake'."""
sourcePath = make_temp_file("import foo")
count, errors = get_errors(sourcePath)
assert count == 1
assert errors == [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))]
@pytest.mark.skipif("PY3")
def test_misencodedFileUTF8():
"""If a source file contains bytes which cannot be decoded, this is reported on stderr."""
SNOWMAN = chr(0x2603)
source = ("""\
# coding: ascii
x = "%s"
""" % SNOWMAN).encode('utf-8')
sourcePath = make_temp_file(source)
assert_contains_output(sourcePath, ["%s: problem decoding source\n" % (sourcePath, )])
def test_misencodedFileUTF16():
"""If a source file contains bytes which cannot be decoded, this is reported on stderr."""
SNOWMAN = chr(0x2603)
source = ("""\
# coding: ascii
x = "%s"
""" % SNOWMAN).encode('utf-16')
sourcePath = make_temp_file(source)
assert_contains_output(sourcePath, ["%s: problem decoding source\n" % (sourcePath,)])
def test_check_recursive():
"""check_recursive descends into each directory, finding Python files and reporting problems."""
tempdir = tempfile.mkdtemp()
os.mkdir(os.path.join(tempdir, 'foo'))
file1 = os.path.join(tempdir, 'foo', 'bar.py')
fd = open(file1, 'wb')
fd.write("import baz\n".encode('ascii'))
fd.close()
file2 = os.path.join(tempdir, 'baz.py')
fd = open(file2, 'wb')
fd.write("import contraband".encode('ascii'))
fd.close()
log = []
reporter = LoggingReporter(log)
warnings = check_recursive([tempdir], reporter)
assert warnings == 2
assert sorted(log) == sorted([('flake', str(UnusedImport(file1, Node(1), 'baz'))),
('flake', str(UnusedImport(file2, Node(1), 'contraband')))])

View File

@ -1,238 +0,0 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import textwrap
import pytest
from pies.overrides import *
from frosted import messages as m
from .utils import flakes
def doctestify(input):
lines = []
for line in textwrap.dedent(input).splitlines():
if line.strip() == '':
pass
elif (line.startswith(' ') or
line.startswith('except:') or
line.startswith('except ') or
line.startswith('finally:') or
line.startswith('else:') or
line.startswith('elif ')):
line = "... %s" % line
else:
line = ">>> %s" % line
lines.append(line)
doctestificator = textwrap.dedent('''\
def doctest_something():
"""
%s
"""
''')
return doctestificator % "\n ".join(lines)
def test_doubleNestingReportsClosestName():
"""Lines in doctest are a bit different so we can't use the test from TestUndefinedNames."""
exc = flakes('''
def doctest_stuff():
"""
>>> def a():
... x = 1
... def b():
... x = 2 # line 7 in the file
... def c():
... x
... x = 3
... return x
... return x
... return x
"""
''', m.UndefinedLocal, run_doctests=True).messages[0]
assert "local variable 'x'" in exc.message and 'line 7' in exc.message
def test_importBeforeDoctest():
flakes("""
import foo
def doctest_stuff():
'''
>>> foo
'''
""", run_doctests=True)
@pytest.mark.skipif("'todo'")
def test_importBeforeAndInDoctest():
flakes('''
import foo
def doctest_stuff():
"""
>>> import foo
>>> foo
"""
foo
''', m.Redefined, run_doctests=True)
def test_importInDoctestAndAfter():
flakes('''
def doctest_stuff():
"""
>>> import foo
>>> foo
"""
import foo
foo()
''', run_doctests=True)
def test_offsetInDoctests():
exc = flakes('''
def doctest_stuff():
"""
>>> x # line 5
"""
''', m.UndefinedName, run_doctests=True).messages[0]
assert exc.lineno == 5
assert exc.col == 12
def test_ignoreErrorsByDefault():
flakes('''
def doctest_stuff():
"""
>>> x # line 5
"""
''')
def test_offsetInLambdasInDoctests():
exc = flakes('''
def doctest_stuff():
"""
>>> lambda: x # line 5
"""
''', m.UndefinedName, run_doctests=True).messages[0]
assert exc.lineno == 5
assert exc.col == 20
def test_offsetAfterDoctests():
exc = flakes('''
def doctest_stuff():
"""
>>> x = 5
"""
x
''', m.UndefinedName, run_doctests=True).messages[0]
assert exc.lineno == 8
assert exc.col == 0
def test_syntax_errorInDoctest():
exceptions = flakes(
'''
def doctest_stuff():
"""
>>> from # line 4
>>> fortytwo = 42
>>> except Exception:
"""
''',
m.DoctestSyntaxError,
m.DoctestSyntaxError, run_doctests=True).messages
exc = exceptions[0]
assert exc.lineno == 4
assert exc.col == 26
exc = exceptions[1]
assert exc.lineno == 6
assert exc.col == 18
def test_indentationErrorInDoctest():
exc = flakes('''
def doctest_stuff():
"""
>>> if True:
... pass
"""
''', m.DoctestSyntaxError, run_doctests=True).messages[0]
assert exc.lineno == 5
assert exc.col == 16
def test_offsetWithMultiLineArgs():
(exc1, exc2) = flakes(
'''
def doctest_stuff(arg1,
arg2,
arg3):
"""
>>> assert
>>> this
"""
''',
m.DoctestSyntaxError,
m.UndefinedName, run_doctests=True).messages
assert exc1.lineno == 6
assert exc1.col == 19
assert exc2.lineno == 7
assert exc2.col == 12
def test_doctestCanReferToFunction():
flakes("""
def foo():
'''
>>> foo
'''
""", run_doctests=True)
def test_doctestCanReferToClass():
flakes("""
class Foo():
'''
>>> Foo
'''
def bar(self):
'''
>>> Foo
'''
""", run_doctests=True)
def test_noOffsetSyntaxErrorInDoctest():
exceptions = flakes('''
def buildurl(base, *args, **kwargs):
"""
>>> buildurl('/blah.php', ('a', '&'), ('b', '=')
'/blah.php?a=%26&b=%3D'
>>> buildurl('/blah.php', a='&', 'b'='=')
'/blah.php?b=%3D&a=%26'
"""
pass
''',
m.DoctestSyntaxError,
m.DoctestSyntaxError, run_doctests=True).messages
exc = exceptions[0]
assert exc.lineno == 4
exc = exceptions[1]
assert exc.lineno == 6

View File

@ -1,174 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
import sys
from pies.overrides import *
from frosted import messages as m
from .utils import flakes
def test_ok():
flakes('''
def foo(a):
pass
foo(5)
''')
flakes('''
def foo(a, b=2):
pass
foo(5, b=1)
''')
def test_noCheckDecorators():
flakes('''
def decorator(f):
return f
@decorator
def foo():
pass
foo(42)
''')
def test_tooManyArguments():
flakes('''
def foo():
pass
foo(5)
''', m.TooManyArguments)
flakes('''
def foo(a, b):
pass
foo(5, 6, 7)
''', m.TooManyArguments)
def test_tooManyArgumentsVarargs():
flakes('''
def foo(a, *args):
pass
foo(1, 2, 3)
''')
def test_unexpectedArgument():
flakes('''
def foo(a):
pass
foo(1, b=3)
''', m.UnexpectedArgument)
flakes('''
def foo(a, *args):
pass
foo(1, b=3)
''', m.UnexpectedArgument)
flakes('''
def foo(a, **kwargs):
pass
foo(1, b=3)
''')
def test_multipleValuesForArgument():
flakes('''
def foo(a):
pass
foo(5, a=5)
''', m.MultipleValuesForArgument)
def test_tooFewArguments():
flakes('''
def foo(a):
pass
foo()
''', m.TooFewArguments)
flakes('''
def foo(a):
pass
foo(*[])
''')
flakes('''
def foo(a):
pass
foo(**{})
''')
def test_tooFewArgumentsVarArgs():
flakes('''
def foo(a, b, *args):
pass
foo(1)
''', m.TooFewArguments)
if PY3:
def test_kwOnlyArguments():
flakes('''
def foo(a, *, b=0):
pass
foo(5, b=2)
''')
flakes('''
def foo(a, *, b=0):
pass
foo(5)
''')
flakes('''
def foo(a, *, b):
pass
foo(5, b=2)
''')
flakes('''
def foo(a, *, b):
pass
foo(5, **{})
''')
flakes('''
def foo(a, *, b):
pass
foo(1)
''', m.NeedKwOnlyArgument)
flakes('''
def foo(a, *args, b):
pass
foo(1, 2, 3, 4)
''', m.NeedKwOnlyArgument)
elif PY2:
def test_compoundArguments():
flakes('''
def foo(a, (b, c)):
pass
foo(1, [])''')
flakes('''
def foo(a, (b, c)):
pass
foo(1, 2, 3)''', m.TooManyArguments)
flakes('''
def foo(a, (b, c)):
pass
foo(1)''', m.TooFewArguments)
flakes('''
def foo(a, (b, c)):
pass
foo(1, b=2, c=3)''', m.UnexpectedArgument)

View File

@ -1,836 +0,0 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from sys import version_info
import pytest
from pies.overrides import *
from frosted import messages as m
from .utils import flakes
def test_unusedImport():
flakes('import fu, bar', m.UnusedImport, m.UnusedImport)
flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport)
def test_aliasedImport():
flakes('import fu as FU, bar as FU',
m.RedefinedWhileUnused, m.UnusedImport)
flakes('from moo import fu as FU, bar as FU',
m.RedefinedWhileUnused, m.UnusedImport)
def test_usedImport():
flakes('import fu; print(fu)')
flakes('from baz import fu; print(fu)')
flakes('import fu; del fu')
def test_redefinedWhileUnused():
flakes('import fu; fu = 3', m.RedefinedWhileUnused)
flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused)
flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused)
def test_redefinedIf():
"""Test that importing a module twice within an if block does raise a warning."""
flakes('''
i = 2
if i==1:
import os
import os
os.path''', m.RedefinedWhileUnused)
def test_redefinedIfElse():
"""Test that importing a module twice in if and else blocks does not raise a warning."""
flakes('''
i = 2
if i==1:
import os
else:
import os
os.path''')
def test_redefinedTry():
"""Test that importing a module twice in an try block does raise a warning."""
flakes('''
try:
import os
import os
except Exception:
pass
os.path''', m.RedefinedWhileUnused)
def test_redefinedTryExcept():
"""Test that importing a module twice in an try and except block does not raise a warning."""
flakes('''
try:
import os
except Exception:
import os
os.path''')
def test_redefinedTryNested():
"""Test that importing a module twice using a nested try/except and if blocks does not issue a warning."""
flakes('''
try:
if True:
if True:
import os
except Exception:
import os
os.path''')
def test_redefinedTryExceptMulti():
flakes("""
try:
from aa import mixer
except AttributeError:
from bb import mixer
except RuntimeError:
from cc import mixer
except Exception:
from dd import mixer
mixer(123)
""")
def test_redefinedTryElse():
flakes("""
try:
from aa import mixer
except ImportError:
pass
else:
from bb import mixer
mixer(123)
""", m.RedefinedWhileUnused)
def test_redefinedTryExceptElse():
flakes("""
try:
import funca
except ImportError:
from bb import funca
from bb import funcb
else:
from bbb import funcb
print(funca, funcb)
""")
def test_redefinedTryExceptFinally():
flakes("""
try:
from aa import a
except ImportError:
from bb import a
finally:
a = 42
print(a)
""")
def test_redefinedTryExceptElseFinally():
flakes("""
try:
import b
except ImportError:
b = Ellipsis
from bb import a
else:
from aa import a
finally:
a = 42
print(a, b)
""")
def test_redefinedByFunction():
flakes('''
import fu
def fu():
pass
''', m.RedefinedWhileUnused)
def test_redefinedInNestedFunction():
"""Test that shadowing a global name with a nested function definition generates a warning."""
flakes('''
import fu
def bar():
def baz():
def fu():
pass
''', m.RedefinedWhileUnused, m.UnusedImport)
def test_redefinedByClass():
flakes('''
import fu
class fu:
pass
''', m.RedefinedWhileUnused)
def test_redefinedBySubclass():
"""If an imported name is redefined by a class statement
which also uses that name in the bases list, no warning is emitted.
"""
flakes('''
from fu import bar
class bar(bar):
pass
''')
def test_redefinedInClass():
"""Test that shadowing a global with a class attribute does not produce a warning."""
flakes('''
import fu
class bar:
fu = 1
print(fu)
''')
def test_usedInFunction():
flakes('''
import fu
def fun():
print(fu)
''')
def test_shadowedByParameter():
flakes('''
import fu
def fun(fu):
print(fu)
''', m.UnusedImport)
flakes('''
import fu
def fun(fu):
print(fu)
print(fu)
''')
def test_newAssignment():
flakes('fu = None')
def test_usedInGetattr():
flakes('import fu; fu.bar.baz')
flakes('import fu; "bar".fu.baz', m.UnusedImport)
def test_usedInSlice():
flakes('import fu; print(fu.bar[1:])')
def test_usedInIfBody():
flakes('''
import fu
if True: print(fu)
''')
def test_usedInIfConditional():
flakes('''
import fu
if fu: pass
''')
def test_usedInElifConditional():
flakes('''
import fu
if False: pass
elif fu: pass
''')
def test_usedInElse():
flakes('''
import fu
if False: pass
else: print(fu)
''')
def test_usedInCall():
flakes('import fu; fu.bar()')
def test_usedInClass():
flakes('''
import fu
class bar:
bar = fu
''')
def test_usedInClassBase():
flakes('''
import fu
class bar(object, fu.baz):
pass
''')
def test_notUsedInNestedScope():
flakes('''
import fu
def bleh():
pass
print(fu)
''')
def test_usedInFor():
flakes('''
import fu
for bar in range(9):
print(fu)
''')
def test_usedInForElse():
flakes('''
import fu
for bar in range(10):
pass
else:
print(fu)
''')
def test_redefinedByFor():
flakes('''
import fu
for fu in range(2):
pass
''', m.RedefinedWhileUnused)
def test_shadowedByFor():
"""Test that shadowing a global name with a for loop variable generates a warning."""
flakes('''
import fu
fu.bar()
for fu in ():
pass
''', m.ImportShadowedByLoopVar)
def test_shadowedByForDeep():
"""Test that shadowing a global name with a for loop variable nested in a tuple unpack generates a warning."""
flakes('''
import fu
fu.bar()
for (x, y, z, (a, b, c, (fu,))) in ():
pass
''', m.ImportShadowedByLoopVar)
def test_usedInReturn():
flakes('''
import fu
def fun():
return fu
''')
def test_usedInOperators():
flakes('import fu; 3 + fu.bar')
flakes('import fu; 3 % fu.bar')
flakes('import fu; 3 - fu.bar')
flakes('import fu; 3 * fu.bar')
flakes('import fu; 3 ** fu.bar')
flakes('import fu; 3 / fu.bar')
flakes('import fu; 3 // fu.bar')
flakes('import fu; -fu.bar')
flakes('import fu; ~fu.bar')
flakes('import fu; 1 == fu.bar')
flakes('import fu; 1 | fu.bar')
flakes('import fu; 1 & fu.bar')
flakes('import fu; 1 ^ fu.bar')
flakes('import fu; 1 >> fu.bar')
flakes('import fu; 1 << fu.bar')
def test_usedInAssert():
flakes('import fu; assert fu.bar')
def test_usedInSubscript():
flakes('import fu; fu.bar[1]')
def test_usedInLogic():
flakes('import fu; fu and False')
flakes('import fu; fu or False')
flakes('import fu; not fu.bar')
def test_usedInList():
flakes('import fu; [fu]')
def test_usedInTuple():
flakes('import fu; (fu,)')
def test_usedInTry():
flakes('''
import fu
try: fu
except Exception: pass
''')
def test_usedInExcept():
flakes('''
import fu
try: fu
except Exception: pass
''')
def test_redefinedByExcept():
as_exc = ', ' if version_info < (2, 6) else ' as '
flakes('''
import fu
try: pass
except Exception%sfu: pass
''' % as_exc, m.RedefinedWhileUnused)
def test_usedInRaise():
flakes('''
import fu
raise fu.bar
''')
def test_usedInYield():
flakes('''
import fu
def gen():
yield fu
''')
def test_usedInDict():
flakes('import fu; {fu:None}')
flakes('import fu; {1:fu}')
def test_usedInParameterDefault():
flakes('''
import fu
def f(bar=fu):
pass
''')
def test_usedInAttributeAssign():
flakes('import fu; fu.bar = 1')
def test_usedInKeywordArg():
flakes('import fu; fu.bar(stuff=fu)')
def test_usedInAssignment():
flakes('import fu; bar=fu')
flakes('import fu; n=0; n+=fu')
def test_usedInListComp():
flakes('import fu; [fu for _ in range(1)]')
flakes('import fu; [1 for _ in range(1) if fu]')
def test_redefinedByListComp():
flakes('import fu; [1 for fu in range(1)]', m.RedefinedWhileUnused)
def test_usedInTryFinally():
flakes('''
import fu
try: pass
finally: fu
''')
flakes('''
import fu
try: fu
finally: pass
''')
def test_usedInWhile():
flakes('''
import fu
while 0:
fu
''')
flakes('''
import fu
while fu: pass
''')
def test_usedInGlobal():
flakes('''
import fu
def f(): global fu
''', m.UnusedImport)
@pytest.mark.skipif("version_info >= (3,)")
def test_usedInBackquote():
flakes('import fu; `fu`')
def test_usedInExec():
if version_info < (3,):
exec_stmt = 'exec "print 1" in fu.bar'
else:
exec_stmt = 'exec("print(1)", fu.bar)'
flakes('import fu; %s' % exec_stmt)
def test_usedInLambda():
flakes('import fu; lambda: fu')
def test_shadowedByLambda():
flakes('import fu; lambda fu: fu', m.UnusedImport)
def test_usedInSliceObj():
flakes('import fu; "meow"[::fu]')
def test_unusedInNestedScope():
flakes('''
def bar():
import fu
fu
''', m.UnusedImport, m.UndefinedName)
def test_methodsDontUseClassScope():
flakes('''
class bar:
import fu
def fun():
fu
''', m.UnusedImport, m.UndefinedName)
def test_nestedFunctionsNestScope():
flakes('''
def a():
def b():
fu
import fu
''')
def test_nestedClassAndFunctionScope():
flakes('''
def a():
import fu
class b:
def c():
print(fu)
''')
def test_importStar():
flakes('from fu import *', m.ImportStarUsed, ignore_frosted_errors=[])
def test_packageImport():
"""If a dotted name is imported and used, no warning is reported."""
flakes('''
import fu.bar
fu.bar
''')
def test_unusedPackageImport():
"""If a dotted name is imported and not used, an unused import warning is reported."""
flakes('import fu.bar', m.UnusedImport)
def test_duplicateSubmoduleImport():
"""If a submodule of a package is imported twice, an unused
import warning and a redefined while unused warning are reported.
"""
flakes('''
import fu.bar, fu.bar
fu.bar
''', m.RedefinedWhileUnused)
flakes('''
import fu.bar
import fu.bar
fu.bar
''', m.RedefinedWhileUnused)
def test_differentSubmoduleImport():
"""If two different submodules of a package are imported,
no duplicate import warning is reported for the package.
"""
flakes('''
import fu.bar, fu.baz
fu.bar, fu.baz
''')
flakes('''
import fu.bar
import fu.baz
fu.bar, fu.baz
''')
def test_assignRHSFirst():
flakes('import fu; fu = fu')
flakes('import fu; fu, bar = fu')
flakes('import fu; [fu, bar] = fu')
flakes('import fu; fu += fu')
def test_tryingMultipleImports():
flakes('''
try:
import fu
except ImportError:
import bar as fu
fu
''')
def test_nonGlobalDoesNotRedefine():
flakes('''
import fu
def a():
fu = 3
return fu
fu
''')
def test_functionsRunLater():
flakes('''
def a():
fu
import fu
''')
def test_functionNamesAreBoundNow():
flakes('''
import fu
def fu():
fu
fu
''', m.RedefinedWhileUnused)
def test_ignoreNonImportRedefinitions():
flakes('a = 1; a = 2')
@pytest.mark.skipif("'todo'")
def test_importingForImportError():
flakes('''
try:
import fu
except ImportError:
pass
''')
@pytest.mark.skipif("'todo: requires evaluating attribute access'")
def test_importedInClass():
"""Imports in class scope can be used through."""
flakes('''
class c:
import i
def __init__():
i
''')
def test_futureImport():
"""__future__ is special."""
flakes('from __future__ import division')
flakes('''
"docstring is allowed before future import"
from __future__ import division
''')
def test_futureImportFirst():
"""__future__ imports must come before anything else."""
flakes('''
x = 5
from __future__ import division
''', m.LateFutureImport)
flakes('''
from foo import bar
from __future__ import division
bar
''', m.LateFutureImport)
def test_ignoredInFunction():
"""An C{__all__} definition does not suppress unused import warnings in a function scope."""
flakes('''
def foo():
import bar
__all__ = ["bar"]
''', m.UnusedImport, m.UnusedVariable)
def test_ignoredInClass():
"""An C{__all__} definition does not suppress unused import warnings in a class scope."""
flakes('''
class foo:
import bar
__all__ = ["bar"]
''', m.UnusedImport)
def test_warningSuppressed():
"""If a name is imported and unused but is named in C{__all__}, no warning is reported."""
flakes('''
import foo
__all__ = ["foo"]
''')
def test_unrecognizable():
"""If C{__all__} is defined in a way that can't be recognized statically, it is ignored."""
flakes('''
import foo
__all__ = ["f" + "oo"]
''', m.UnusedImport)
flakes('''
import foo
__all__ = [] + ["foo"]
''', m.UnusedImport)
def test_unboundExported():
"""If C{__all__} includes a name which is not bound, a warning is emitted."""
flakes('''
__all__ = ["foo"]
''', m.UndefinedExport)
# Skip this in __init__.py though, since the rules there are a little
# different.
for filename in ["foo/__init__.py", "__init__.py"]:
flakes('''
__all__ = ["foo"]
''', filename=filename, **{'ignore_frosted_errors_for___init__.py': ['E101', 'E103']})
def test_importStarExported():
"""Do not report undefined if import * is used"""
flakes('''
from foolib import *
__all__ = ["foo"]
''', m.ImportStarUsed)
def test_usedInGenExp():
"""Using a global in a generator expression results in no warnings."""
flakes('import fu; (fu for _ in range(1))')
flakes('import fu; (1 for _ in range(1) if fu)')
def test_redefinedByGenExp():
""" Re-using a global name as the loop variable for a generator
expression results in a redefinition warning.
"""
flakes('import fu; (1 for fu in range(1))', m.RedefinedWhileUnused, m.UnusedImport)
def test_usedAsDecorator():
"""Using a global name in a decorator statement results in no warnings, but
using an undefined name in a decorator statement results in an undefined
name warning.
"""
flakes('''
from interior import decorate
@decorate
def f():
return "hello"
''')
flakes('''
from interior import decorate
@decorate('value')
def f():
return "hello"
''')
flakes('''
@decorate
def f():
return "hello"
''', m.UndefinedName)
def test_usedAsClassDecorator():
"""Using an imported name as a class decorator results in no warnings
but using an undefined name as a class decorator results in an undefined name warning.
"""
flakes('''
from interior import decorate
@decorate
class foo:
pass
''')
flakes('''
from interior import decorate
@decorate("foo")
class bar:
pass
''')
flakes('''
@decorate
class foo:
pass
''', m.UndefinedName)

View File

@ -1,53 +0,0 @@
from frosted import messages as m
from frosted.api import _noqa_lines, _re_noqa, check
from frosted.reporter import Reporter
from .utils import LoggingReporter, flakes
def test_regex():
# simple format
assert _re_noqa.search('#noqa')
assert _re_noqa.search('# noqa')
# simple format is strict, must be at start of comment
assert not _re_noqa.search('# foo noqa')
# verbose format (not strict like simple format)
assert _re_noqa.search('#frosted:noqa')
assert _re_noqa.search('# frosted: noqa')
assert _re_noqa.search('# foo frosted: noqa')
def test_checker_ignore_lines():
# ignore same line
flakes('from fu import *', ignore_lines=[1])
# don't ignore different line
flakes('from fu import *', m.ImportStarUsed, ignore_lines=[2])
def test_noqa_lines():
assert _noqa_lines('from fu import bar; bar') == []
assert _noqa_lines('from fu import * # noqa; bar') == [1]
assert _noqa_lines('from fu import * #noqa\nbar\nfoo # frosted: noqa') == [1, 3]
def test_check_integration():
""" make sure all the above logic comes together correctly in the check() function """
output = []
reporter = LoggingReporter(output)
result = check('from fu import *', 'test', reporter, not_ignore_frosted_errors=['E103'])
# errors reported
assert result == 1
assert "unable to detect undefined names" in output.pop(0)[1]
# same test, but with ignore set
output = []
reporter = LoggingReporter(output)
result = check('from fu import * # noqa', 'test', reporter)
# errors reported
assert result == 0
assert len(output) == 0

View File

@ -1,886 +0,0 @@
"""Tests for various Frosted behavior."""
from __future__ import absolute_import, division, print_function, unicode_literals
from sys import version_info
import pytest
from pies.overrides import *
from frosted import messages as m
from .utils import flakes
def test_duplicateArgs():
flakes('def fu(bar, bar): pass', m.DuplicateArgument)
@pytest.mark.skipif("'todo: this requires finding all assignments in the function body first'")
def test_localReferencedBeforeAssignment():
flakes('''
a = 1
def f():
a; a=1
f()
''', m.UndefinedName)
def test_redefinedInListComp():
"""Test that shadowing a variable in a list comprehension raises a warning."""
flakes('''
a = 1
[1 for a, b in [(1, 2)]]
''', m.RedefinedInListComp)
flakes('''
class A:
a = 1
[1 for a, b in [(1, 2)]]
''', m.RedefinedInListComp)
flakes('''
def f():
a = 1
[1 for a, b in [(1, 2)]]
''', m.RedefinedInListComp)
flakes('''
[1 for a, b in [(1, 2)]]
[1 for a, b in [(1, 2)]]
''')
flakes('''
for a, b in [(1, 2)]:
pass
[1 for a, b in [(1, 2)]]
''')
def test_redefinedInGenerator():
"""Test that reusing a variable in a generator does not raise a warning."""
flakes('''
a = 1
(1 for a, b in [(1, 2)])
''')
flakes('''
class A:
a = 1
list(1 for a, b in [(1, 2)])
''')
flakes('''
def f():
a = 1
(1 for a, b in [(1, 2)])
''', m.UnusedVariable)
flakes('''
(1 for a, b in [(1, 2)])
(1 for a, b in [(1, 2)])
''')
flakes('''
for a, b in [(1, 2)]:
pass
(1 for a, b in [(1, 2)])
''')
@pytest.mark.skipif('''version_info < (2, 7)''')
def test_redefinedInSetComprehension():
"""Test that reusing a variable in a set comprehension does not raise a warning."""
flakes('''
a = 1
{1 for a, b in [(1, 2)]}
''')
flakes('''
class A:
a = 1
{1 for a, b in [(1, 2)]}
''')
flakes('''
def f():
a = 1
{1 for a, b in [(1, 2)]}
''', m.UnusedVariable)
flakes('''
{1 for a, b in [(1, 2)]}
{1 for a, b in [(1, 2)]}
''')
flakes('''
for a, b in [(1, 2)]:
pass
{1 for a, b in [(1, 2)]}
''')
@pytest.mark.skipif('''version_info < (2, 7)''')
def test_redefinedInDictComprehension():
"""Test that reusing a variable in a dict comprehension does not raise a warning."""
flakes('''
a = 1
{1: 42 for a, b in [(1, 2)]}
''')
flakes('''
class A:
a = 1
{1: 42 for a, b in [(1, 2)]}
''')
flakes('''
def f():
a = 1
{1: 42 for a, b in [(1, 2)]}
''', m.UnusedVariable)
flakes('''
{1: 42 for a, b in [(1, 2)]}
{1: 42 for a, b in [(1, 2)]}
''')
flakes('''
for a, b in [(1, 2)]:
pass
{1: 42 for a, b in [(1, 2)]}
''')
def test_redefinedFunction():
"""Test that shadowing a function definition with another one raises a warning."""
flakes('''
def a(): pass
def a(): pass
''', m.RedefinedWhileUnused)
def test_redefinedClassFunction():
"""Test that shadowing a function definition in a class suite with another one raises a warning."""
flakes('''
class A:
def a(): pass
def a(): pass
''', m.RedefinedWhileUnused)
def test_redefinedIfElseFunction():
"""Test that shadowing a function definition twice in an if and else block does not raise a warning."""
flakes('''
if True:
def a(): pass
else:
def a(): pass
''')
def test_redefinedIfFunction():
"""Test that shadowing a function definition within an if block raises a warning."""
flakes('''
if True:
def a(): pass
def a(): pass
''', m.RedefinedWhileUnused)
def test_redefinedTryExceptFunction():
"""Test that shadowing a function definition twice in try and except block does not raise a warning."""
flakes('''
try:
def a(): pass
except Exception:
def a(): pass
''')
def test_redefinedTryFunction():
"""Test that shadowing a function definition within a try block raises a warning."""
flakes('''
try:
def a(): pass
def a(): pass
except Exception:
pass
''', m.RedefinedWhileUnused)
def test_redefinedIfElseInListComp():
"""Test that shadowing a variable in a list comprehension in an if and else block does not raise a warning."""
flakes('''
if False:
a = 1
else:
[a for a in '12']
''')
def test_redefinedElseInListComp():
"""Test that shadowing a variable in a list comprehension in an else (or if) block raises a warning."""
flakes('''
if False:
pass
else:
a = 1
[a for a in '12']
''', m.RedefinedInListComp)
def test_functionDecorator():
"""Test that shadowing a function definition with a decorated version of that function does not raise a warning."""
flakes('''
from somewhere import somedecorator
def a(): pass
a = somedecorator(a)
''')
def test_classFunctionDecorator():
"""Test that shadowing a function definition in a class suite with a
decorated version of that function does not raise a warning.
"""
flakes('''
class A:
def a(): pass
a = classmethod(a)
''')
@pytest.mark.skipif('''version_info < (2, 6)''')
def test_modernProperty():
flakes("""
class A:
@property
def t():
pass
@t.setter
def t(self, value):
pass
@t.deleter
def t():
pass
""")
def test_unaryPlus():
"""Don't die on unary +."""
flakes('+1')
def test_undefinedBaseClass():
"""If a name in the base list of a class definition is undefined, a warning is emitted."""
flakes('''
class foo(foo):
pass
''', m.UndefinedName)
def test_classNameUndefinedInClassBody():
"""If a class name is used in the body of that class's definition and the
name is not already defined, a warning is emitted.
"""
flakes('''
class foo:
foo
''', m.UndefinedName)
def test_classNameDefinedPreviously():
"""If a class name is used in the body of that class's definition and the
name was previously defined in some other way, no warning is emitted.
"""
flakes('''
foo = None
class foo:
foo
''')
def test_classRedefinition():
"""If a class is defined twice in the same module, a warning is emitted."""
flakes('''
class Foo:
pass
class Foo:
pass
''', m.RedefinedWhileUnused)
def test_functionRedefinedAsClass():
"""If a function is redefined as a class, a warning is emitted."""
flakes('''
def Foo():
pass
class Foo:
pass
''', m.RedefinedWhileUnused)
def test_classRedefinedAsFunction():
"""If a class is redefined as a function, a warning is emitted."""
flakes('''
class Foo:
pass
def Foo():
pass
''', m.RedefinedWhileUnused)
@pytest.mark.skipif("'todo: Too hard to make this warn but other cases stay silent'")
def test_doubleAssignment():
"""If a variable is re-assigned to without being used, no warning is emitted."""
flakes('''
x = 10
x = 20
''', m.RedefinedWhileUnused)
def test_doubleAssignmentConditionally():
"""If a variable is re-assigned within a conditional, no warning is emitted."""
flakes('''
x = 10
if True:
x = 20
''')
def test_doubleAssignmentWithUse():
"""If a variable is re-assigned to after being used, no warning is emitted."""
flakes('''
x = 10
y = x * 2
x = 20
''')
def test_comparison():
"""If a defined name is used on either side of any of the six comparison operators, no warning is emitted."""
flakes('''
x = 10
y = 20
x < y
x <= y
x == y
x != y
x >= y
x > y
''')
def test_identity():
"""If a defined name is used on either side of an identity test, no warning is emitted."""
flakes('''
x = 10
y = 20
x is y
x is not y
''')
def test_containment():
"""If a defined name is used on either side of a containment test, no warning is emitted."""
flakes('''
x = 10
y = 20
x in y
x not in y
''')
def test_loopControl():
"""break and continue statements are supported."""
flakes('''
for x in [1, 2]:
break
''')
flakes('''
for x in [1, 2]:
continue
''')
def test_ellipsis():
"""Ellipsis in a slice is supported."""
flakes('''
[1, 2][...]
''')
def test_extendedSlice():
"""Extended slices are supported."""
flakes('''
x = 3
[1, 2][x,:]
''')
def test_varAugmentedAssignment():
"""Augmented assignment of a variable is supported.
We don't care about var refs.
"""
flakes('''
foo = 0
foo += 1
''')
def test_attrAugmentedAssignment():
"""Augmented assignment of attributes is supported.
We don't care about attr refs.
"""
flakes('''
foo = None
foo.bar += foo.baz
''')
def test_unusedVariable():
"""Warn when a variable in a function is assigned a value that's never used."""
flakes('''
def a():
b = 1
''', m.UnusedVariable)
def test_unusedVariableAsLocals():
"""Using locals() it is perfectly valid to have unused variables."""
flakes('''
def a():
b = 1
return locals()
''')
def test_unusedVariableNoLocals():
"""Using locals() in wrong scope should not matter."""
flakes('''
def a():
locals()
def a():
b = 1
return
''', m.UnusedVariable)
def test_assignToGlobal():
"""Assigning to a global and then not using that global is perfectly
acceptable.
Do not mistake it for an unused local variable.
"""
flakes('''
b = 0
def a():
global b
b = 1
''')
@pytest.mark.skipif('''version_info < (3,)''')
def test_assignToNonlocal():
"""Assigning to a nonlocal and then not using that binding is perfectly
acceptable.
Do not mistake it for an unused local variable.
"""
flakes('''
b = b'0'
def a():
nonlocal b
b = b'1'
''')
def test_assignToMember():
"""Assigning to a member of another object and then not using that member
variable is perfectly acceptable.
Do not mistake it for an unused local variable.
"""
# XXX: Adding this test didn't generate a failure. Maybe not
# necessary?
flakes('''
class b:
pass
def a():
b.foo = 1
''')
def test_assignInForLoop():
"""Don't warn when a variable in a for loop is assigned to but not used."""
flakes('''
def f():
for i in range(10):
pass
''')
def test_assignInListComprehension():
"""Don't warn when a variable in a list comprehension is assigned to but not used."""
flakes('''
def f():
[None for i in range(10)]
''')
def test_generatorExpression():
"""Don't warn when a variable in a generator expression is assigned to but not used."""
flakes('''
def f():
(None for i in range(10))
''')
def test_assignmentInsideLoop():
"""Don't warn when a variable assignment occurs lexically after its use."""
flakes('''
def f():
x = None
for i in range(10):
if i > 2:
return x
x = i * 2
''')
def test_tupleUnpacking():
"""Don't warn when a variable included in tuple unpacking is unused.
It's very common for variables in a tuple unpacking assignment to be unused in good Python code, so warning will
only create false positives.
"""
flakes('''
def f():
(x, y) = 1, 2
''')
def test_listUnpacking():
"""Don't warn when a variable included in list unpacking is unused."""
flakes('''
def f():
[x, y] = [1, 2]
''')
def test_closedOver():
"""Don't warn when the assignment is used in an inner function."""
flakes('''
def barMaker():
foo = 5
def bar():
return foo
return bar
''')
def test_doubleClosedOver():
"""Don't warn when the assignment is used in an inner function, even if
that inner function itself is in an inner function."""
flakes('''
def barMaker():
foo = 5
def bar():
def baz():
return foo
return bar
''')
def test_tracebackhideSpecialVariable():
"""Do not warn about unused local variable __tracebackhide__, which is a
special variable for py.test."""
flakes("""
def helper():
__tracebackhide__ = True
""")
def test_ifexp():
"""Test C{foo if bar else baz} statements."""
flakes("a = 'moo' if True else 'oink'")
flakes("a = foo if True else 'oink'", m.UndefinedName)
flakes("a = 'moo' if True else bar", m.UndefinedName)
def test_withStatementNoNames():
"""No warnings are emitted for using inside or after a nameless statement a name defined beforehand."""
flakes('''
from __future__ import with_statement
bar = None
with open("foo"):
bar
bar
''')
def test_withStatementSingleName():
"""No warnings are emitted for using a name defined by a statement within the suite or afterwards."""
flakes('''
from __future__ import with_statement
with open('foo') as bar:
bar
bar
''')
def test_withStatementAttributeName():
"""No warnings are emitted for using an attribute as the target of a statement."""
flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo.bar:
pass
''')
def test_withStatementSubscript():
"""No warnings are emitted for using a subscript as the target of a statement."""
flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo[0]:
pass
''')
def test_withStatementSubscriptUndefined():
"""An undefined name warning is emitted if the subscript used as the target of a with statement is not defined."""
flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo[bar]:
pass
''', m.UndefinedName)
def test_withStatementTupleNames():
"""No warnings are emitted for using any of the tuple of names defined
by a statement within the suite or afterwards.
"""
flakes('''
from __future__ import with_statement
with open('foo') as (bar, baz):
bar, baz
bar, baz
''')
def test_withStatementListNames():
"""No warnings are emitted for using any of the list of names defined by
a statement within the suite or afterwards.
"""
flakes('''
from __future__ import with_statement
with open('foo') as [bar, baz]:
bar, baz
bar, baz
''')
def test_withStatementComplicatedTarget():
""" If the target of a statement uses any or all of the valid forms
for that part of the grammar
(See: http://docs.python.org/reference/compound_stmts.html#the-with-statement),
the names involved are checked both for definedness and any bindings
created are respected in the suite of the statement and afterwards.
"""
flakes('''
from __future__ import with_statement
c = d = e = g = h = i = None
with open('foo') as [(a, b), c[d], e.f, g[h:i]]:
a, b, c, d, e, g, h, i
a, b, c, d, e, g, h, i
''')
def test_withStatementSingleNameUndefined():
"""An undefined name warning is emitted if the name first defined by a statement is used before the statement."""
flakes('''
from __future__ import with_statement
bar
with open('foo') as bar:
pass
''', m.UndefinedName)
def test_withStatementTupleNamesUndefined():
""" An undefined name warning is emitted if a name first defined by a the
tuple-unpacking form of the statement is used before the statement.
"""
flakes('''
from __future__ import with_statement
baz
with open('foo') as (bar, baz):
pass
''', m.UndefinedName)
def test_withStatementSingleNameRedefined():
"""A redefined name warning is emitted if a name bound by an import is
rebound by the name defined by a statement.
"""
flakes('''
from __future__ import with_statement
import bar
with open('foo') as bar:
pass
''', m.RedefinedWhileUnused)
def test_withStatementTupleNamesRedefined():
""" A redefined name warning is emitted if a name bound by an import is
rebound by one of the names defined by the tuple-unpacking form of a
statement.
"""
flakes('''
from __future__ import with_statement
import bar
with open('foo') as (bar, baz):
pass
''', m.RedefinedWhileUnused)
def test_withStatementUndefinedInside():
"""An undefined name warning is emitted if a name is used inside the body
of a statement without first being bound.
"""
flakes('''
from __future__ import with_statement
with open('foo') as bar:
baz
''', m.UndefinedName)
def test_withStatementNameDefinedInBody():
"""A name defined in the body of a statement can be used after the body ends without warning."""
flakes('''
from __future__ import with_statement
with open('foo') as bar:
baz = 10
baz
''')
def test_withStatementUndefinedInExpression():
"""An undefined name warning is emitted if a name in the I{test} expression of a statement is undefined."""
flakes('''
from __future__ import with_statement
with bar as baz:
pass
''', m.UndefinedName)
flakes('''
from __future__ import with_statement
with bar as bar:
pass
''', m.UndefinedName)
@pytest.mark.skipif('''version_info < (2, 7)''')
def test_dictComprehension():
"""Dict comprehensions are properly handled."""
flakes('''
a = {1: x for x in range(10)}
''')
@pytest.mark.skipif('''version_info < (2, 7)''')
def test_setComprehensionAndLiteral():
"""Set comprehensions are properly handled."""
flakes('''
a = {1, 2, 3}
b = {x for x in range(10)}
''')
def test_exceptionUsedInExcept():
as_exc = ', ' if version_info < (2, 6) else ' as '
flakes('''
try: pass
except Exception%se: e
''' % as_exc)
flakes('''
def download_review():
try: pass
except Exception%se: e
''' % as_exc)
def test_exceptWithoutNameInFunction():
"""Don't issue false warning when an unnamed exception is used.
Previously, there would be a false warning, but only when the try..except was in a function
"""
flakes('''
import tokenize
def foo():
try: pass
except tokenize.TokenError: pass
''')
def test_exceptWithoutNameInFunctionTuple():
"""Don't issue false warning when an unnamed exception is used.
This example catches a tuple of exception types.
"""
flakes('''
import tokenize
def foo():
try: pass
except (tokenize.TokenError, IndentationError): pass
''')
def test_augmentedAssignmentImportedFunctionCall():
"""Consider a function that is called on the right part of an augassign operation to be used."""
flakes('''
from foo import bar
baz = 0
baz += bar()
''')
@pytest.mark.skipif('''version_info < (3, 3)''')
def test_yieldFromUndefined():
"""Test yield from statement."""
flakes('''
def bar():
yield from foo()
''', m.UndefinedName)
def test_bareExcept():
"""
Issue a warning when using bare except:.
"""
flakes('''
try:
pass
except:
pass
''', m.BareExcept)
def test_access_debug():
"""Test accessing __debug__ returns no errors"""
flakes('''
if __debug__:
print("success!")
print(__debug__)
''')

View File

@ -1,71 +0,0 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from sys import version_info
import pytest
from pies.overrides import *
from frosted import messages as m
from .utils import flakes
@pytest.mark.skipif("version_info >= (3,)")
def test_return():
flakes('''
class a:
def b():
for x in a.c:
if x:
yield x
return a
''', m.ReturnWithArgsInsideGenerator)
@pytest.mark.skipif("version_info >= (3,)")
def test_returnNone():
flakes('''
def a():
yield 12
return None
''', m.ReturnWithArgsInsideGenerator)
@pytest.mark.skipif("version_info >= (3,)")
def test_returnYieldExpression():
flakes('''
def a():
b = yield a
return b
''', m.ReturnWithArgsInsideGenerator)
@pytest.mark.skipif("version_info >= (3,)")
def test_return_with_args_inside_generator_not_duplicated():
# doubly nested - should still only complain once
flakes('''
def f0():
def f1():
yield None
return None
''', m.ReturnWithArgsInsideGenerator)
# triple nested - should still only complain once
flakes('''
def f0():
def f1():
def f2():
yield None
return None
''', m.ReturnWithArgsInsideGenerator)
@pytest.mark.skipif("version_info >= (3,)")
def test_no_false_positives_for_return_inside_generator():
# doubly nested - should still only complain once
flakes('''
def f():
def g():
yield None
return g
''')

View File

@ -1,181 +0,0 @@
"""frosted/test/test_script.py.
Tests functionality (integration testing) that require starting a full frosted instance against input files
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
"""
from __future__ import absolute_import, division, print_function
import os
import shutil
import subprocess
import sys
import tempfile
import pytest
from pies.overrides import *
import frosted
from frosted.api import iter_source_code
from frosted.messages import UnusedImport
from .utils import Node
FROSTED_BINARY = os.path.join(os.path.dirname(frosted.__file__), 'main.py')
def setup_function(function):
globals()['TEMP_DIR'] = tempfile.mkdtemp()
globals()['TEMP_FILE_PATH'] = os.path.join(TEMP_DIR, 'temp')
def teardown_function(function):
shutil.rmtree(TEMP_DIR)
def make_empty_file(*parts):
assert parts
fpath = os.path.join(TEMP_DIR, *parts)
fd = open(fpath, 'a')
fd.close()
return fpath
def test_emptyDirectory():
"""There are no Python files in an empty directory."""
assert list(iter_source_code([TEMP_DIR])) == []
def test_singleFile():
"""If the directory contains one Python file - iter_source_code will find it"""
childpath = make_empty_file('foo.py')
assert list(iter_source_code([TEMP_DIR])) == [childpath]
def test_onlyPythonSource():
"""Files that are not Python source files are not included."""
make_empty_file('foo.pyc')
assert list(iter_source_code([TEMP_DIR])) == []
def test_recurses():
"""If the Python files are hidden deep down in child directories, we will find them."""
os.mkdir(os.path.join(TEMP_DIR, 'foo'))
apath = make_empty_file('foo', 'a.py')
os.mkdir(os.path.join(TEMP_DIR, 'bar'))
bpath = make_empty_file('bar', 'b.py')
cpath = make_empty_file('c.py')
assert sorted(iter_source_code([TEMP_DIR])) == sorted([apath, bpath, cpath])
def test_multipleDirectories():
"""iter_source_code can be given multiple directories - it will recurse into each of them"""
foopath = os.path.join(TEMP_DIR, 'foo')
barpath = os.path.join(TEMP_DIR, 'bar')
os.mkdir(foopath)
apath = make_empty_file('foo', 'a.py')
os.mkdir(barpath)
bpath = make_empty_file('bar', 'b.py')
assert sorted(iter_source_code([foopath, barpath])) == sorted([apath, bpath])
def test_explicitFiles():
"""If one of the paths given to iter_source_code is not a directory but a
file, it will include that in its output."""
epath = make_empty_file('e.py')
assert list(iter_source_code([epath])) == [epath]
def run_frosted(paths, stdin=None):
"""Launch a subprocess running frosted."""
env = native_dict(os.environ)
env['PYTHONPATH'] = os.pathsep.join(sys.path)
command = [sys.executable, FROSTED_BINARY]
command.extend(paths)
if stdin:
p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = p.communicate(stdin)
else:
p = subprocess.Popen(command, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = p.communicate()
rv = p.wait()
if PY3:
stdout = stdout.decode('utf-8')
stderr = stderr.decode('utf-8')
return (stdout, stderr, rv)
def test_goodFile():
"""When a Python source file is all good, the return code is zero and no
messages are printed to either stdout or stderr.
"""
fd = open(TEMP_FILE_PATH, 'a')
fd.close()
d = run_frosted([TEMP_FILE_PATH])
assert d == ('', '', 0)
def test_fileWithFlakes():
""" When a Python source file has warnings,
the return code is non-zero and the warnings are printed to stdout
"""
fd = open(TEMP_FILE_PATH, 'wb')
fd.write("import contraband\n".encode('ascii'))
fd.close()
d = run_frosted([TEMP_FILE_PATH])
expected = UnusedImport(TEMP_FILE_PATH, Node(1), 'contraband')
assert d[0].strip() == expected.message.strip()
@pytest.mark.skipif("sys.version_info >= (3,)")
def test_non_unicode_slash_u():
""" Ensure \ u doesn't cause a unicode decode error """
fd = open(TEMP_FILE_PATH, 'wb')
fd.write('"""Example: C:\\foobar\\unit-tests\\test.py"""'.encode('ascii'))
fd.close()
d = run_frosted([TEMP_FILE_PATH])
assert d == ('', '', 0)
def test_errors():
""" When frosted finds errors with the files it's given, (if they don't exist, say),
then the return code is non-zero and the errors are printed to stderr
"""
d = run_frosted([TEMP_FILE_PATH, '-r'])
error_msg = '%s: No such file or directory\n' % (TEMP_FILE_PATH,)
assert d == ('', error_msg, 1)
def test_readFromStdin():
"""If no arguments are passed to C{frosted} then it reads from stdin."""
d = run_frosted(['-'], stdin='import contraband'.encode('ascii'))
expected = UnusedImport('<stdin>', Node(1), 'contraband')
assert d[0].strip() == expected.message.strip()
@pytest.mark.skipif("sys.version_info >= (3,)")
def test_print_statement_python2():
d = run_frosted(['-'], stdin='print "Hello, Frosted"'.encode('ascii'))
assert d == ('', '', 0)

View File

@ -1,407 +0,0 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from sys import version_info
import pytest
from pies.overrides import *
from _ast import PyCF_ONLY_AST
from frosted import messages as m
from frosted import checker
from.utils import flakes
def test_undefined():
flakes('bar', m.UndefinedName)
def test_definedInListComp():
flakes('[a for a in range(10) if a]')
def test_functionsNeedGlobalScope():
flakes('''
class a:
def b():
fu
fu = 1
''')
def test_builtins():
flakes('range(10)')
def test_builtinWindowsError():
"""WindowsError is sometimes a builtin name, so no warning is emitted for using it."""
flakes('WindowsError')
def test_magicGlobalsFile():
"""Use of the __file magic global should not emit an undefined name
warning."""
flakes('__file__')
def test_magicGlobalsBuiltins():
"""Use of the __builtins magic global should not emit an undefined name warning."""
flakes('__builtins__')
def test_magicGlobalImport():
"""Use of the __import__ magic global should not emit an undefined name warning."""
flakes('__import__')
def test_magicGlobalsName():
"""Use of the __name magic global should not emit an undefined name warning."""
flakes('__name__')
def test_magicGlobalsPath():
"""Use of the __path magic global should not emit an undefined name warning,
if you refer to it from a file called __init__.py.
"""
flakes('__path__', m.UndefinedName)
flakes('__path__', filename='package/__init__.py')
def test_globalImportStar():
"""Can't find undefined names with import *."""
flakes('from fu import *; bar', m.ImportStarUsed)
def test_localImportStar():
"""A local import * still allows undefined names to be found in upper scopes."""
flakes('''
def a():
from fu import *
bar
''', m.ImportStarUsed, m.UndefinedName)
@pytest.mark.skipif("version_info >= (3,)")
def test_unpackedParameter():
"""Unpacked function parameters create bindings."""
flakes('''
def a((bar, baz)):
bar; baz
''')
@pytest.mark.skipif("'todo'")
def test_definedByGlobal():
""""global" can make an otherwise undefined name in another function defined."""
flakes('''
def a(): global fu; fu = 1
def b(): fu
''')
def test_globalInGlobalScope():
"""A global statement in the global scope is ignored."""
flakes('''
global x
def foo():
print(x)
''', m.UndefinedName)
def test_del():
"""Del deletes bindings."""
flakes('a = 1; del a; a', m.UndefinedName)
def test_delGlobal():
"""Del a global binding from a function."""
flakes('''
a = 1
def f():
global a
del a
a
''')
def test_delUndefined():
"""Del an undefined name."""
flakes('del a', m.UndefinedName)
def test_globalFromNestedScope():
"""Global names are available from nested scopes."""
flakes('''
a = 1
def b():
def c():
a
''')
def test_laterRedefinedGlobalFromNestedScope():
"""Test that referencing a local name that shadows a global, before it is
defined, generates a warning."""
flakes('''
a = 1
def fun():
a
a = 2
return a
''', m.UndefinedLocal)
def test_laterRedefinedGlobalFromNestedScope2():
"""Test that referencing a local name in a nested scope that shadows a
global declared in an enclosing scope, before it is defined, generates a
warning."""
flakes('''
a = 1
def fun():
global a
def fun2():
a
a = 2
return a
''', m.UndefinedLocal)
def test_intermediateClassScopeIgnored():
"""If a name defined in an enclosing scope is shadowed by a local variable
and the name is used locally before it is bound, an unbound local warning
is emitted, even if there is a class scope between the enclosing scope and
the local scope."""
flakes('''
def f():
x = 1
class g:
def h():
a = x
x = None
print(x, a)
print(x)
''', m.UndefinedLocal)
def test_doubleNestingReportsClosestName():
"""Test that referencing a local name in a nested scope that shadows a
variable declared in two different outer scopes before it is defined in the
innermost scope generates an UnboundLocal warning which refers to the
nearest shadowed name."""
exc = flakes('''
def a():
x = 1
def b():
x = 2 # line 5
def c():
x
x = 3
return x
return x
return x
''', m.UndefinedLocal).messages[0]
assert 'x' in exc.message
assert str(5) in exc.message
def test_laterRedefinedGlobalFromNestedScope3():
"""Test that referencing a local name in a nested scope that shadows a
global, before it is defined, generates a warning."""
flakes('''
def fun():
a = 1
def fun2():
a
a = 1
return a
return a
''', m.UndefinedLocal)
def test_undefinedAugmentedAssignment():
flakes(
'''
def f(seq):
a = 0
seq[a] += 1
seq[b] /= 2
c[0] *= 2
a -= 3
d += 4
e[any] = 5
''',
m.UndefinedName, # b
m.UndefinedName, # c
m.UndefinedName, m.UnusedVariable, # d
m.UndefinedName, # e
)
def test_nestedClass():
"""Nested classes can access enclosing scope."""
flakes('''
def f(foo):
class C:
bar = foo
def f():
return foo
return C()
f(123).f()
''')
def test_badNestedClass():
"""Free variables in nested classes must bind at class creation."""
flakes('''
def f():
class C:
bar = foo
foo = 456
return foo
f()
''', m.UndefinedName)
def test_definedAsStarArgs():
"""Star and double-star arg names are defined."""
flakes('''
def f(a, *b, **c):
print(a, b, c)
''')
@pytest.mark.skipif("version_info < (3,)")
def test_definedAsStarUnpack():
"""Star names in unpack are defined."""
flakes('''
a, *b = range(10)
print(a, b)
''')
flakes('''
*a, b = range(10)
print(a, b)
''')
flakes('''
a, *b, c = range(10)
print(a, b, c)
''')
@pytest.mark.skipif("version_info < (3,)")
def test_keywordOnlyArgs():
"""Keyword-only arg names are defined."""
flakes('''
def f(*, a, b=None):
print(a, b)
''')
flakes('''
import default_b
def f(*, a, b=default_b):
print(a, b)
''')
@pytest.mark.skipif("version_info < (3,)")
def test_keywordOnlyArgsUndefined():
"""Typo in kwonly name."""
flakes('''
def f(*, a, b=default_c):
print(a, b)
''', m.UndefinedName)
@pytest.mark.skipif("version_info < (3,)")
def test_annotationUndefined():
"""Undefined annotations."""
flakes('''
from abc import note1, note2, note3, note4, note5
def func(a: note1, *args: note2,
b: note3=12, **kw: note4) -> note5: pass
''')
flakes('''
def func():
d = e = 42
def func(a: {1, d}) -> (lambda c: e): pass
''')
@pytest.mark.skipif("version_info < (3,)")
def test_metaClassUndefined():
flakes('''
from abc import ABCMeta
class A(metaclass=ABCMeta): pass
''')
def test_definedInGenExp():
"""Using the loop variable of a generator expression results in no
warnings."""
flakes('(a for a in %srange(10) if a)' %
('x' if version_info < (3,) else ''))
def test_undefinedWithErrorHandler():
"""Some compatibility code checks explicitly for NameError.
It should not trigger warnings.
"""
flakes('''
try:
socket_map
except NameError:
socket_map = {}
''')
flakes('''
try:
_memoryview.contiguous
except (NameError, AttributeError):
raise RuntimeError("Python >= 3.3 is required")
''')
# If NameError is not explicitly handled, generate a warning
flakes('''
try:
socket_map
except Exception:
socket_map = {}
''', m.UndefinedName)
flakes('''
try:
socket_map
except Exception:
socket_map = {}
''', m.UndefinedName)
def test_definedInClass():
"""Defined name for generator expressions and dict/set comprehension."""
flakes('''
class A:
T = range(10)
Z = (x for x in T)
L = [x for x in T]
B = dict((i, str(i)) for i in T)
''')
if version_info >= (2, 7):
flakes('''
class A:
T = range(10)
X = {x for x in T}
Y = {x:x for x in T}
''')
def test_impossibleContext():
"""A Name node with an unrecognized context results in a RuntimeError being raised."""
tree = compile("x = 10", "<test>", "exec", PyCF_ONLY_AST)
# Make it into something unrecognizable.
tree.body[0].targets[0].ctx = object()
with pytest.raises(RuntimeError):
checker.Checker(tree)

View File

@ -1,49 +0,0 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import textwrap
from collections import namedtuple
from pies.overrides import *
from frosted import checker
PyCF_ONLY_AST = 1024
__all__ = ['flakes', 'Node', 'LoggingReporter']
def flakes(input, *expectedOutputs, **kw):
tree = compile(textwrap.dedent(input), "<test>", "exec", PyCF_ONLY_AST)
results = checker.Checker(tree, **kw)
outputs = [message.type for message in results.messages]
expectedOutputs = list(expectedOutputs)
outputs.sort(key=lambda t: t.name)
expectedOutputs.sort(key=lambda t: t.name)
assert outputs == expectedOutputs, ('\n'
'for input:\n'
'%s\n'
'expected outputs:\n'
'%r\n'
'but got:\n'
'%s') % (input, expectedOutputs,
'\n'.join([str(o) for o in results.messages]))
return results
class Node(namedtuple('Node', ['lineno', 'col_offset'])):
"""A mock AST Node."""
def __new__(cls, lineno, col_offset=0):
return super(Node, cls).__new__(cls, lineno, col_offset)
class LoggingReporter(namedtuple('LoggingReporter', ['log'])):
"""A mock Reporter implementation."""
def flake(self, message):
self.log.append(('flake', str(message)))
def unexpected_error(self, filename, message):
self.log.append(('unexpected_error', filename, message))
def syntax_error(self, filename, msg, lineno, offset, line):
self.log.append(('syntax_error', filename, msg, lineno, offset, line))

View File

@ -1,23 +1,20 @@
"""
future: Easy, safe support for Python 3/2 compatibility
future: Easy, safe support for Python 2/3 compatibility
=======================================================
``future`` is the missing compatibility layer between Python 3 and Python
2. It allows you to use a single, clean Python 3.x-compatible codebase to
support both Python 3 and Python 2 with minimal overhead.
Notable projects that use ``future`` for Python 2/3 compatibility are
`Mezzanine <http://mezzanine.jupo.org/>`_ and `ObsPy <http://obspy.org>`_.
``future`` is the missing compatibility layer between Python 2 and Python
3. It allows you to use a single, clean Python 3.x-compatible codebase to
support both Python 2 and Python 3 with minimal overhead.
It is designed to be used as follows::
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from future.builtins import (
bytes, dict, int, list, object, range, str,
ascii, chr, hex, input, next, oct, open,
pow, round, super,
filter, map, zip)
from builtins import (
bytes, dict, int, list, object, range, str,
ascii, chr, hex, input, next, oct, open,
pow, round, super,
filter, map, zip)
followed by predominantly standard, idiomatic Python 3 code that then runs
similarly on Python 2.6/2.7 and Python 3.3+.
@ -30,13 +27,31 @@ versus 2, to provide their Python 3 semantics.
Standard library reorganization
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``from future import standard_library`` provides a context-manager called
``hooks`` that installs import hooks (PEP 3108) to allow renamed and
moved standard library modules to be imported from their new Py3 locations.
``future`` supports the standard library reorganization (PEP 3108) through the
following Py3 interfaces:
>>> # Top-level packages with Py3 names provided on Py2:
>>> import configparser
>>> import html.parser
>>> import queue
>>> import tkinter.dialog
>>> import xmlrpc.client
>>> # etc.
>>> # Aliases provided for extensions to existing Py2 module names:
>>> from future.standard_library import install_aliases
>>> install_aliases()
>>> from collections import Counter, OrderedDict # backported to Py2.6
>>> from collections import UserDict, UserList, UserString
>>> import urllib.request
>>> from itertools import filterfalse, zip_longest
>>> from subprocess import getoutput, getstatusoutput
Automatic conversion
--------------------
An included script called `futurize
<http://python-future.org/automatic_conversion.html>`_ aids in converting
code (from either Python 2 or Python 3) to code compatible with both
@ -62,7 +77,7 @@ Credits
Licensing
---------
Copyright 2013-2014 Python Charmers Pty Ltd, Australia.
Copyright 2013-2015 Python Charmers Pty Ltd, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
"""
@ -70,10 +85,10 @@ The software is distributed under an MIT licence. See LICENSE.txt.
__title__ = 'future'
__author__ = 'Ed Schofield'
__license__ = 'MIT'
__copyright__ = 'Copyright 2014 Python Charmers Pty Ltd'
__copyright__ = 'Copyright 2013-2015 Python Charmers Pty Ltd'
__ver_major__ = 0
__ver_minor__ = 14
__ver_patch__ = 2
__ver_patch__ = 3
__ver_sub__ = ''
__version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__,
__ver_patch__, __ver_sub__)

View File

@ -11,7 +11,7 @@ collections.Counter (for Python 2.6)
from math import ceil as oldceil
import subprocess
from future.utils import iteritems, PY26
from future.utils import iteritems, itervalues, PY26
def ceil(x):

View File

@ -299,20 +299,29 @@ class newint(with_metaclass(BaseNewInt, long)):
used to represent the integer. If signed is False and a negative integer
is given, an OverflowError is raised.
"""
if signed:
raise NotImplementedError("Not yet implemented. Please contribute a patch at http://python-future.org")
if length < 0:
raise ValueError("length argument must be non-negative")
if length == 0 and self == 0:
return newbytes()
if signed and self < 0:
bits = length * 8
num = (2**bits) + self
if num <= 0:
raise OverflowError("int too smal to convert")
else:
if self < 0:
raise OverflowError("can't convert negative int to unsigned")
num = self
if byteorder not in ('little', 'big'):
raise ValueError("byteorder must be either 'little' or 'big'")
if length < 0:
raise ValueError("length argument must be non-negative")
if length == 0 and num == 0:
return newbytes()
h = b'%x' % num
s = newbytes((b'0'*(len(h) % 2) + h).zfill(length*2).decode('hex'))
if signed:
high_set = s[0] & 0x80
if self > 0 and high_set:
raise OverflowError("int too big to convert")
if self < 0 and not high_set:
raise OverflowError("int too small to convert")
if len(s) > length:
raise OverflowError("int too big to convert")
return s if byteorder == 'big' else s[::-1]
@ -335,8 +344,6 @@ class newint(with_metaclass(BaseNewInt, long)):
The signed keyword-only argument indicates whether two's complement is
used to represent the integer.
"""
if signed:
raise NotImplementedError("Not yet implemented. Please contribute a patch at http://python-future.org")
if byteorder not in ('little', 'big'):
raise ValueError("byteorder must be either 'little' or 'big'")
if isinstance(mybytes, unicode):
@ -351,6 +358,8 @@ class newint(with_metaclass(BaseNewInt, long)):
# The encode() method has been disabled by newbytes, but Py2's
# str has it:
num = int(native(b).encode('hex'), 16)
if signed and (b[0] & 0x80):
num = num - (2 ** (len(b)*8))
return cls(num)

View File

@ -1,5 +1,5 @@
"""
Nearly identical to xrange.py, by Dan Crosta, from
Nearly identical to xrange.py, by Dan Crosta, from
https://github.com/dcrosta/xrange.git
@ -18,10 +18,8 @@ From Dan Crosta's README:
https://late.am/post/2012/06/18/what-the-heck-is-an-xrange
"""
from math import ceil
from collections import Sequence, Iterator
from future.utils import PY3
from itertools import islice
class newrange(Sequence):
@ -58,16 +56,28 @@ class newrange(Sequence):
self._step = step
self._len = (stop - start) // step + bool((stop - start) % step)
@property
def start(self):
return self._start
@property
def stop(self):
return self._stop
@property
def step(self):
return self._step
def __repr__(self):
if self._step == 1:
return 'range(%d, %d)' % (self._start, self._stop)
return 'range(%d, %d, %d)' % (self._start, self._stop, self._step)
def __eq__(self, other):
return isinstance(other, newrange) and \
self._start == other._start and \
self._stop == other._stop and \
self._step == other._step
return (isinstance(other, newrange) and
(self._len == 0 == other._len or
(self._start, self._step, self._len) ==
(other._start, other._step, self._len)))
def __len__(self):
return self._len
@ -97,12 +107,7 @@ class newrange(Sequence):
return False
def __reversed__(self):
"""Return a range which represents a sequence whose
contents are the same as the sequence this range
represents, but in the opposite order."""
sign = self._step / abs(self._step)
last = self._start + ((self._len - 1) * self._step)
return newrange(last, self._start - sign, -1 * self._step)
return iter(self[::-1])
def __getitem__(self, index):
"""Return the element at position ``index`` in the sequence
@ -121,56 +126,35 @@ class newrange(Sequence):
"""Return a range which represents the requested slce
of the sequence represented by this range.
"""
start, stop, step = slce.start, slce.stop, slce.step
if step == 0:
raise ValueError('slice step cannot be 0')
start = start or self._start
stop = stop or self._stop
if start < 0:
start = max(0, start + self._len)
if stop < 0:
stop = max(start, stop + self._len)
if step is None or step > 0:
return newrange(start, stop, step or 1)
else:
rv = reversed(self)
rv._step = step
return rv
start, stop, step = slce.indices(self._len)
return newrange(self._start + self._step*start,
self._start + stop,
self._step * step)
def __iter__(self):
"""Return an iterator which enumerates the elements of the
sequence this range represents."""
return rangeiterator(self)
return range_iterator(self)
class rangeiterator(Iterator):
class range_iterator(Iterator):
"""An iterator for a :class:`range`.
"""
def __init__(self, rangeobj):
self._range = rangeobj
# Intialize the "last outputted value" to the value
# just before the first value; this simplifies next()
self._last = self._range._start - self._range._step
self._count = 0
def __init__(self, range_):
self._stepper = islice(_count(range_.start, range_.step), len(range_))
def __iter__(self):
"""An iterator is already an iterator, so return ``self``.
"""
return self
def next(self):
"""Return the next element in the sequence represented
by the range we are iterating, or raise StopIteration
if we have passed the end of the sequence."""
self._last += self._range._step
self._count += 1
if self._count > self._range._len:
raise StopIteration()
return self._last
return next(self._stepper)
# itertools.count in Py 2.6 doesn't accept a step parameter
def _count(start=0, step=1):
while True:
yield start
start += step
__all__ = ['newrange']

View File

@ -2,6 +2,7 @@
# pep8.py - Check Python source code formatting, according to PEP 8
# Copyright (C) 2006-2009 Johann C. Rocholl <johann@rocholl.net>
# Copyright (C) 2009-2014 Florent Xicluna <florent.xicluna@gmail.com>
# Copyright (C) 2014 Ian Lee <ianlee1521@gmail.com>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
@ -46,8 +47,6 @@ W warnings
"""
from __future__ import with_statement
__version__ = '1.5.7'
import os
import sys
import re
@ -63,13 +62,21 @@ try:
except ImportError:
from ConfigParser import RawConfigParser
DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__'
DEFAULT_IGNORE = 'E123,E226,E24'
if sys.platform == 'win32':
DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8')
else:
DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or
os.path.expanduser('~/.config'), 'pep8')
__version__ = '1.6.2'
DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox'
DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704'
try:
if sys.platform == 'win32':
USER_CONFIG = os.path.expanduser(r'~\.pep8')
else:
USER_CONFIG = os.path.join(
os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'),
'pep8'
)
except ImportError:
USER_CONFIG = None
PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep8')
TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite')
MAX_LINE_LENGTH = 79
@ -101,8 +108,9 @@ ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b')
DOCSTRING_REGEX = re.compile(r'u?r?["\']')
EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]')
WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)')
COMPARE_SINGLETON_REGEX = re.compile(r'([=!]=)\s*(None|False|True)')
COMPARE_NEGATIVE_REGEX = re.compile(r'\b(not)\s+[^[({ ]+\s+(in|is)\s')
COMPARE_SINGLETON_REGEX = re.compile(r'\b(None|False|True)?\s*([=!]=)'
r'\s*(?(1)|(None|False|True))\b')
COMPARE_NEGATIVE_REGEX = re.compile(r'\b(not)\s+[^][)(}{ ]+\s+(in|is)\s')
COMPARE_TYPE_REGEX = re.compile(r'(?:[=!]=|is(?:\s+not)?)\s*type(?:s.\w+Type'
r'|\s*\(\s*([^)]*[^ )])\s*\))')
KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS))
@ -353,20 +361,25 @@ def indentation(logical_line, previous_logical, indent_char,
Okay: a = 1
Okay: if a == 0:\n a = 1
E111: a = 1
E114: # a = 1
Okay: for item in items:\n pass
E112: for item in items:\npass
E115: for item in items:\n# Hi\n pass
Okay: a = 1\nb = 2
E113: a = 1\n b = 2
E116: a = 1\n # b = 2
"""
if indent_char == ' ' and indent_level % 4:
yield 0, "E111 indentation is not a multiple of four"
c = 0 if logical_line else 3
tmpl = "E11%d %s" if logical_line else "E11%d %s (comment)"
if indent_level % 4:
yield 0, tmpl % (1 + c, "indentation is not a multiple of four")
indent_expect = previous_logical.endswith(':')
if indent_expect and indent_level <= previous_indent_level:
yield 0, "E112 expected an indented block"
if indent_level > previous_indent_level and not indent_expect:
yield 0, "E113 unexpected indentation"
yield 0, tmpl % (2 + c, "expected an indented block")
elif not indent_expect and indent_level > previous_indent_level:
yield 0, tmpl % (3 + c, "unexpected indentation")
def continued_indentation(logical_line, tokens, indent_level, hang_closing,
@ -422,6 +435,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing,
indent_chances = {}
last_indent = tokens[0][2]
visual_indent = None
last_token_multiline = False
# for each depth, memorize the visual indent column
indent = [last_indent[1]]
if verbose >= 3:
@ -501,8 +515,9 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing,
yield start, "%s continuation line %s" % error
# look for visual indenting
if (parens[row] and token_type not in (tokenize.NL, tokenize.COMMENT)
and not indent[depth]):
if (parens[row] and
token_type not in (tokenize.NL, tokenize.COMMENT) and
not indent[depth]):
indent[depth] = start[1]
indent_chances[start[1]] = True
if verbose >= 4:
@ -675,7 +690,7 @@ def missing_whitespace_around_operator(logical_line, tokens):
if need_space is True or need_space[1]:
# A needed trailing space was not found
yield prev_end, "E225 missing whitespace around operator"
else:
elif prev_text != '**':
code, optype = 'E226', 'arithmetic'
if prev_text == '%':
code, optype = 'E228', 'modulo'
@ -743,6 +758,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens):
Okay: boolean(a != b)
Okay: boolean(a <= b)
Okay: boolean(a >= b)
Okay: def foo(arg: int = 42):
E251: def complex(real, imag = 0.0):
E251: return magic(r = real, i = imag)
@ -750,6 +766,8 @@ def whitespace_around_named_parameter_equals(logical_line, tokens):
parens = 0
no_space = False
prev_end = None
annotated_func_arg = False
in_def = logical_line.startswith('def')
message = "E251 unexpected spaces around keyword / parameter equals"
for token_type, text, start, end, line in tokens:
if token_type == tokenize.NL:
@ -758,15 +776,22 @@ def whitespace_around_named_parameter_equals(logical_line, tokens):
no_space = False
if start != prev_end:
yield (prev_end, message)
elif token_type == tokenize.OP:
if token_type == tokenize.OP:
if text == '(':
parens += 1
elif text == ')':
parens -= 1
elif parens and text == '=':
elif in_def and text == ':' and parens == 1:
annotated_func_arg = True
elif parens and text == ',' and parens == 1:
annotated_func_arg = False
elif parens and text == '=' and not annotated_func_arg:
no_space = True
if start != prev_end:
yield (prev_end, message)
if not parens:
annotated_func_arg = False
prev_end = end
@ -787,6 +812,7 @@ def whitespace_before_comment(logical_line, tokens):
E262: x = x + 1 #Increment x
E262: x = x + 1 # Increment x
E265: #Block comment
E266: ### Block comment
"""
prev_end = (0, 0)
for token_type, text, start, end, line in tokens:
@ -797,13 +823,15 @@ def whitespace_before_comment(logical_line, tokens):
yield (prev_end,
"E261 at least two spaces before inline comment")
symbol, sp, comment = text.partition(' ')
bad_prefix = symbol not in ('#', '#:')
bad_prefix = symbol not in '#:' and (symbol.lstrip('#')[:1] or '#')
if inline_comment:
if bad_prefix or comment[:1].isspace():
if bad_prefix or comment[:1] in WHITESPACE:
yield start, "E262 inline comment should start with '# '"
elif bad_prefix:
if text.rstrip('#') and (start[0] > 1 or symbol[1] != '!'):
elif bad_prefix and (bad_prefix != '!' or start[0] > 1):
if bad_prefix != '#':
yield start, "E265 block comment should start with '# '"
elif comment:
yield start, "E266 too many leading '#' for block comment"
elif token_type != tokenize.NL:
prev_end = end
@ -827,6 +855,56 @@ def imports_on_separate_lines(logical_line):
yield found, "E401 multiple imports on one line"
def module_imports_on_top_of_file(
logical_line, indent_level, checker_state, noqa):
r"""Imports are always put at the top of the file, just after any module
comments and docstrings, and before module globals and constants.
Okay: import os
Okay: # this is a comment\nimport os
Okay: '''this is a module docstring'''\nimport os
Okay: r'''this is a module docstring'''\nimport os
Okay: try:\n import x\nexcept:\n pass\nelse:\n pass\nimport y
Okay: try:\n import x\nexcept:\n pass\nfinally:\n pass\nimport y
E402: a=1\nimport os
E402: 'One string'\n"Two string"\nimport os
E402: a=1\nfrom sys import x
Okay: if x:\n import os
"""
def is_string_literal(line):
if line[0] in 'uUbB':
line = line[1:]
if line and line[0] in 'rR':
line = line[1:]
return line and (line[0] == '"' or line[0] == "'")
allowed_try_keywords = ('try', 'except', 'else', 'finally')
if indent_level: # Allow imports in conditional statements or functions
return
if not logical_line: # Allow empty lines or comments
return
if noqa:
return
line = logical_line
if line.startswith('import ') or line.startswith('from '):
if checker_state.get('seen_non_imports', False):
yield 0, "E402 module level import not at top of file"
elif any(line.startswith(kw) for kw in allowed_try_keywords):
# Allow try, except, else, finally keywords intermixed with imports in
# order to support conditional importing
return
elif is_string_literal(line):
# The first literal is a docstring, allow it. Otherwise, report error.
if checker_state.get('seen_docstring', False):
checker_state['seen_non_imports'] = True
else:
checker_state['seen_docstring'] = True
else:
checker_state['seen_non_imports'] = True
def compound_statements(logical_line):
r"""Compound statements (on the same line) are generally discouraged.
@ -834,6 +912,9 @@ def compound_statements(logical_line):
on the same line, never do this for multi-clause statements.
Also avoid folding such long lines!
Always use a def statement instead of an assignment statement that
binds a lambda expression directly to a name.
Okay: if foo == 'blah':\n do_blah_thing()
Okay: do_one()
Okay: do_two()
@ -847,20 +928,30 @@ def compound_statements(logical_line):
E701: try: something()
E701: finally: cleanup()
E701: if foo == 'blah': one(); two(); three()
E702: do_one(); do_two(); do_three()
E703: do_four(); # useless semicolon
E704: def f(x): return 2*x
E731: f = lambda x: 2*x
"""
line = logical_line
last_char = len(line) - 1
found = line.find(':')
while -1 < found < last_char:
before = line[:found]
if (before.count('{') <= before.count('}') and # {'a': 1} (dict)
before.count('[') <= before.count(']') and # [1:2] (slice)
before.count('(') <= before.count(')') and # (Python 3 annotation)
not LAMBDA_REGEX.search(before)): # lambda x: x
yield found, "E701 multiple statements on one line (colon)"
if ((before.count('{') <= before.count('}') and # {'a': 1} (dict)
before.count('[') <= before.count(']') and # [1:2] (slice)
before.count('(') <= before.count(')'))): # (annotation)
lambda_kw = LAMBDA_REGEX.search(before)
if lambda_kw:
before = line[:lambda_kw.start()].rstrip()
if before[-1:] == '=' and isidentifier(before[:-1].strip()):
yield 0, ("E731 do not assign a lambda expression, use a "
"def")
break
if before.startswith('def '):
yield 0, "E704 multiple statements on one line (def)"
else:
yield found, "E701 multiple statements on one line (colon)"
found = line.find(':', found + 1)
found = line.find(';')
while -1 < found:
@ -885,10 +976,15 @@ def explicit_line_join(logical_line, tokens):
Okay: aaa = [123,\n 123]
Okay: aaa = ("bbb "\n "ccc")
Okay: aaa = "bbb " \\n "ccc"
Okay: aaa = 123 # \\
"""
prev_start = prev_end = parens = 0
comment = False
backslash = None
for token_type, text, start, end, line in tokens:
if start[0] != prev_start and parens and backslash:
if token_type == tokenize.COMMENT:
comment = True
if start[0] != prev_start and parens and backslash and not comment:
yield backslash, "E502 the backslash is redundant between brackets"
if end[0] != prev_end:
if line.rstrip('\r\n').endswith('\\'):
@ -905,6 +1001,45 @@ def explicit_line_join(logical_line, tokens):
parens -= 1
def break_around_binary_operator(logical_line, tokens):
r"""
Avoid breaks before binary operators.
The preferred place to break around a binary operator is after the
operator, not before it.
W503: (width == 0\n + height == 0)
W503: (width == 0\n and height == 0)
Okay: (width == 0 +\n height == 0)
Okay: foo(\n -x)
Okay: foo(x\n [])
Okay: x = '''\n''' + ''
Okay: foo(x,\n -y)
Okay: foo(x, # comment\n -y)
"""
def is_binary_operator(token_type, text):
# The % character is strictly speaking a binary operator, but the
# common usage seems to be to put it next to the format parameters,
# after a line break.
return ((token_type == tokenize.OP or text in ['and', 'or']) and
text not in "()[]{},:.;@=%")
line_break = False
unary_context = True
for token_type, text, start, end, line in tokens:
if token_type == tokenize.COMMENT:
continue
if ('\n' in text or '\r' in text) and token_type != tokenize.STRING:
line_break = True
else:
if (is_binary_operator(token_type, text) and line_break and
not unary_context):
yield start, "W503 line break before binary operator"
unary_context = text in '([{,;'
line_break = False
def comparison_to_singleton(logical_line, noqa):
r"""Comparison to singletons should use "is" or "is not".
@ -913,7 +1048,9 @@ def comparison_to_singleton(logical_line, noqa):
Okay: if arg is not None:
E711: if arg != None:
E711: if None == arg:
E712: if arg == True:
E712: if False == arg:
Also, beware of writing if x when you really mean if x is not None --
e.g. when testing whether a variable or argument that defaults to None was
@ -922,8 +1059,9 @@ def comparison_to_singleton(logical_line, noqa):
"""
match = not noqa and COMPARE_SINGLETON_REGEX.search(logical_line)
if match:
same = (match.group(1) == '==')
singleton = match.group(2)
singleton = match.group(1) or match.group(3)
same = (match.group(2) == '==')
msg = "'if cond is %s:'" % (('' if same else 'not ') + singleton)
if singleton in ('None',):
code = 'E711'
@ -932,7 +1070,7 @@ def comparison_to_singleton(logical_line, noqa):
nonzero = ((singleton == 'True' and same) or
(singleton == 'False' and not same))
msg += " or 'if %scond:'" % ('' if nonzero else 'not ')
yield match.start(1), ("%s comparison to %s should be %s" %
yield match.start(2), ("%s comparison to %s should be %s" %
(code, singleton, msg))
@ -957,7 +1095,7 @@ def comparison_negative(logical_line):
yield pos, "E714 test for object identity should be 'is not'"
def comparison_type(logical_line):
def comparison_type(logical_line, noqa):
r"""Object type comparisons should always use isinstance().
Do not compare types directly.
@ -973,7 +1111,7 @@ def comparison_type(logical_line):
Okay: if type(a1) is type(b1):
"""
match = COMPARE_TYPE_REGEX.search(logical_line)
if match:
if match and not noqa:
inst = match.group(1)
if inst and isidentifier(inst) and inst not in SINGLETONS:
return # Allow comparison for types which are not obvious
@ -1039,7 +1177,7 @@ if '' == ''.encode():
"""Read the source code."""
with open(filename, 'rU') as f:
return f.readlines()
isidentifier = re.compile(r'[a-zA-Z_]\w*').match
isidentifier = re.compile(r'[a-zA-Z_]\w*$').match
stdin_get_value = sys.stdin.read
else:
# Python 3
@ -1138,10 +1276,13 @@ def normalize_paths(value, parent=os.curdir):
Return a list of absolute paths.
"""
if not value or isinstance(value, list):
if not value:
return []
if isinstance(value, list):
return value
paths = []
for path in value.split(','):
path = path.strip()
if '/' in path:
path = os.path.abspath(os.path.join(parent, path))
paths.append(path.rstrip('/'))
@ -1158,14 +1299,12 @@ def filename_match(filename, patterns, default=True):
return any(fnmatch(filename, pattern) for pattern in patterns)
def _is_eol_token(token):
return token[0] in NEWLINE or token[4][token[3][1]:].lstrip() == '\\\n'
if COMMENT_WITH_NL:
def _is_eol_token(token):
return (token[0] in NEWLINE or
(token[0] == tokenize.COMMENT and token[1] == token[4]))
else:
def _is_eol_token(token):
return token[0] in NEWLINE
def _is_eol_token(token, _eol_token=_is_eol_token):
return _eol_token(token) or (token[0] == tokenize.COMMENT and
token[1] == token[4])
##############################################################################
# Framework to run all checks
@ -1222,6 +1361,8 @@ class Checker(object):
self.hang_closing = options.hang_closing
self.verbose = options.verbose
self.filename = filename
# Dictionary where a checker can store its custom state.
self._checker_states = {}
if filename is None:
self.filename = 'stdin'
self.lines = lines or []
@ -1277,10 +1418,16 @@ class Checker(object):
arguments.append(getattr(self, name))
return check(*arguments)
def init_checker_state(self, name, argument_names):
""" Prepares a custom state for the specific checker plugin."""
if 'checker_state' in argument_names:
self.checker_state = self._checker_states.setdefault(name, {})
def check_physical(self, line):
"""Run all physical checks on a raw input line."""
self.physical_line = line
for name, check, argument_names in self._physical_checks:
self.init_checker_state(name, argument_names)
result = self.run_check(check, argument_names)
if result is not None:
(offset, text) = result
@ -1308,8 +1455,8 @@ class Checker(object):
(start_row, start_col) = start
if prev_row != start_row: # different row
prev_text = self.lines[prev_row - 1][prev_col - 1]
if prev_text == ',' or (prev_text not in '{[('
and text not in '}])'):
if prev_text == ',' or (prev_text not in '{[(' and
text not in '}])'):
text = ' ' + text
elif prev_col != start_col: # different column
text = line[prev_col:start_col] + text
@ -1325,6 +1472,10 @@ class Checker(object):
"""Build a line from tokens and run all logical checks on it."""
self.report.increment_logical_line()
mapping = self.build_tokens_line()
if not mapping:
return
(start_row, start_col) = mapping[0][1]
start_line = self.lines[start_row - 1]
self.indent_level = expand_indent(start_line[:start_col])
@ -1335,6 +1486,7 @@ class Checker(object):
for name, check, argument_names in self._logical_checks:
if self.verbose >= 4:
print(' ' + name)
self.init_checker_state(name, argument_names)
for offset, text in self.run_check(check, argument_names) or ():
if not isinstance(offset, tuple):
for token_offset, pos in mapping:
@ -1596,6 +1748,14 @@ class StandardReport(BaseReport):
print(re.sub(r'\S', ' ', line[:offset]) + '^')
if self._show_pep8 and doc:
print(' ' + doc.strip())
# stdout is block buffered when not stdout.isatty().
# line can be broken where buffer boundary since other processes
# write to same file.
# flush() after print() to avoid buffer boundary.
# Typical buffer size is 8192. line written safely when
# len(line) < 8192.
sys.stdout.flush()
return self.file_errors
@ -1619,7 +1779,7 @@ class StyleGuide(object):
# build options from the command line
self.checker_class = kwargs.pop('checker_class', Checker)
parse_argv = kwargs.pop('parse_argv', False)
config_file = kwargs.pop('config_file', None)
config_file = kwargs.pop('config_file', False)
parser = kwargs.pop('parser', None)
# build options from dict
options_dict = dict(*args, **kwargs)
@ -1772,7 +1932,8 @@ def get_parser(prog='pep8', version=__version__):
parser.add_option('--select', metavar='errors', default='',
help="select errors and warnings (e.g. E,W6)")
parser.add_option('--ignore', metavar='errors', default='',
help="skip errors and warnings (e.g. E4,W)")
help="skip errors and warnings (e.g. E4,W) "
"(default: %s)" % DEFAULT_IGNORE)
parser.add_option('--show-source', action='store_true',
help="show source code for each error")
parser.add_option('--show-pep8', action='store_true',
@ -1808,24 +1969,39 @@ def get_parser(prog='pep8', version=__version__):
def read_config(options, args, arglist, parser):
"""Read both user configuration and local configuration."""
"""Read and parse configurations
If a config file is specified on the command line with the "--config"
option, then only it is used for configuration.
Otherwise, the user configuration (~/.config/pep8) and any local
configurations in the current directory or above will be merged together
(in that order) using the read method of ConfigParser.
"""
config = RawConfigParser()
user_conf = options.config
if user_conf and os.path.isfile(user_conf):
if options.verbose:
print('user configuration: %s' % user_conf)
config.read(user_conf)
cli_conf = options.config
local_dir = os.curdir
parent = tail = args and os.path.abspath(os.path.commonprefix(args))
while tail:
if config.read([os.path.join(parent, fn) for fn in PROJECT_CONFIG]):
local_dir = parent
if cli_conf and os.path.isfile(cli_conf):
if options.verbose:
print('cli configuration: %s' % cli_conf)
config.read(cli_conf)
else:
if USER_CONFIG and os.path.isfile(USER_CONFIG):
if options.verbose:
print('local configuration: in %s' % parent)
break
(parent, tail) = os.path.split(parent)
print('user configuration: %s' % USER_CONFIG)
config.read(USER_CONFIG)
parent = tail = args and os.path.abspath(os.path.commonprefix(args))
while tail:
if config.read(os.path.join(parent, fn) for fn in PROJECT_CONFIG):
local_dir = parent
if options.verbose:
print('local configuration: in %s' % parent)
break
(parent, tail) = os.path.split(parent)
pep8_section = parser.prog
if config.has_section(pep8_section):
@ -1863,19 +2039,21 @@ def read_config(options, args, arglist, parser):
def process_options(arglist=None, parse_argv=False, config_file=None,
parser=None):
"""Process options passed either via arglist or via command line args."""
"""Process options passed either via arglist or via command line args.
Passing in the ``config_file`` parameter allows other tools, such as flake8
to specify their own options to be processed in pep8.
"""
if not parser:
parser = get_parser()
if not parser.has_option('--config'):
if config_file is True:
config_file = DEFAULT_CONFIG
group = parser.add_option_group("Configuration", description=(
"The project options are read from the [%s] section of the "
"tox.ini file or the setup.cfg file located in any parent folder "
"of the path(s) being processed. Allowed options are: %s." %
(parser.prog, ', '.join(parser.config_options))))
group.add_option('--config', metavar='path', default=config_file,
help="user config file location (default: %default)")
help="user config file location")
# Don't read the command line if the module is used as a library.
if not arglist and not parse_argv:
arglist = []
@ -1920,7 +2098,7 @@ def _main():
except AttributeError:
pass # not supported on Windows
pep8style = StyleGuide(parse_argv=True, config_file=True)
pep8style = StyleGuide(parse_argv=True)
options = pep8style.options
if options.doctest or options.testsuite:
from testsuite.support import run_tests

View File

@ -1,11 +0,0 @@
from __future__ import absolute_import
from .version_info import PY3
if PY3:
from StringIO import *
else:
try:
from cStringIO import *
except ImportError:
from StringIO import *

View File

@ -1,31 +0,0 @@
"""pies/__init__.py.
Adds necessary hooks to allow Python code to run on multiple major versions of Python at once
(currently 2.6 - 3.x)
Usage:
Anywhere you want to gain support for multiple versions of Python simply add the following two lines
from __future__ import absolute_import, division, print_function, unicode_literals
from pies.overrides import *
And for changed stdlibs:
from pies import [libname]
Copyright (C) 2013 Timothy Edmund Crosley
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
"""
__version__ = "2.6.1"

View File

@ -1,88 +0,0 @@
"""pies/_utils.py.
Utils internal to the pies library and not meant for direct external usage.
Copyright (C) 2013 Timothy Edmund Crosley
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
"""
import abc
import sys
def with_metaclass(meta, *bases):
"""Enables use of meta classes across Python Versions. taken from jinja2/_compat.py.
Use it like this::
class BaseForm(object):
pass
class FormType(type):
pass
class Form(with_metaclass(FormType, BaseForm)):
pass
"""
class metaclass(meta):
__call__ = type.__call__
__init__ = type.__init__
def __new__(cls, name, this_bases, d):
if this_bases is None:
return type.__new__(cls, name, (), d)
return meta(name, bases, d)
return metaclass('temporary_class', None, {})
def unmodified_isinstance(*bases):
"""When called in the form
MyOverrideClass(unmodified_isinstance(BuiltInClass))
it allows calls against passed in built in instances to pass even if there not a subclass
"""
class UnmodifiedIsInstance(type):
if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
@classmethod
def __instancecheck__(cls, instance):
if cls.__name__ in (str(base.__name__) for base in bases):
return isinstance(instance, bases)
subclass = getattr(instance, '__class__', None)
subtype = type(instance)
instance_type = getattr(abc, '_InstanceType', None)
if not instance_type:
class test_object:
pass
instance_type = type(test_object)
if subtype is instance_type:
subtype = subclass
if subtype is subclass or subclass is None:
return cls.__subclasscheck__(subtype)
return (cls.__subclasscheck__(subclass) or cls.__subclasscheck__(subtype))
else:
@classmethod
def __instancecheck__(cls, instance):
if cls.__name__ in (str(base.__name__) for base in bases):
return isinstance(instance, bases)
return type.__instancecheck__(cls, instance)
return with_metaclass(UnmodifiedIsInstance, *bases)

View File

@ -1,30 +0,0 @@
from __future__ import absolute_import
import sys
from ast import *
from .version_info import PY2
if PY2 or sys.version_info[1] <= 2:
Try = TryExcept
else:
TryFinally = ()
if PY2:
def argument_names(node):
return [isinstance(arg, Name) and arg.id or None for arg in node.args.args]
def kw_only_argument_names(node):
return []
def kw_only_default_count(node):
return 0
else:
def argument_names(node):
return [arg.arg for arg in node.args.args]
def kw_only_argument_names(node):
return [arg.arg for arg in node.args.kwonlyargs]
def kw_only_default_count(node):
return sum(1 for n in node.args.kw_defaults if n is not None)

View File

@ -1,13 +0,0 @@
from __future__ import absolute_import
from collections import *
from .version_info import PY2
if PY2:
from UserString import *
from UserList import *
import sys
if sys.version_info < (2, 7):
from ordereddict import OrderedDict

View File

@ -1,98 +0,0 @@
from __future__ import absolute_import
import sys
from functools import *
from .version_info import PY2
if PY2:
reduce = reduce
if sys.version_info < (3, 2):
try:
from threading import Lock
except ImportError:
from dummy_threading import Lock
from .collections import OrderedDict
def lru_cache(maxsize=100):
"""Least-recently-used cache decorator.
Taking from: https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py
with slight modifications.
If *maxsize* is set to None, the LRU features are disabled and the cache
can grow without bound.
Arguments to the cached function must be hashable.
View the cache statistics named tuple (hits, misses, maxsize, currsize) with
f.cache_info(). Clear the cache and statistics with f.cache_clear().
Access the underlying function with f.__wrapped__.
See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
"""
def decorating_function(user_function, tuple=tuple, sorted=sorted, len=len, KeyError=KeyError):
hits, misses = [0], [0]
kwd_mark = (object(),) # separates positional and keyword args
lock = Lock()
if maxsize is None:
CACHE = dict()
@wraps(user_function)
def wrapper(*args, **kwds):
key = args
if kwds:
key += kwd_mark + tuple(sorted(kwds.items()))
try:
result = CACHE[key]
hits[0] += 1
return result
except KeyError:
pass
result = user_function(*args, **kwds)
CACHE[key] = result
misses[0] += 1
return result
else:
CACHE = OrderedDict()
@wraps(user_function)
def wrapper(*args, **kwds):
key = args
if kwds:
key += kwd_mark + tuple(sorted(kwds.items()))
with lock:
cached = CACHE.get(key, None)
if cached:
del CACHE[key]
CACHE[key] = cached
hits[0] += 1
return cached
result = user_function(*args, **kwds)
with lock:
CACHE[key] = result # record recent use of this key
misses[0] += 1
while len(CACHE) > maxsize:
CACHE.popitem(last=False)
return result
def cache_info():
"""Report CACHE statistics."""
with lock:
return _CacheInfo(hits[0], misses[0], maxsize, len(CACHE))
def cache_clear():
"""Clear the CACHE and CACHE statistics."""
with lock:
CACHE.clear()
hits[0] = misses[0] = 0
wrapper.cache_info = cache_info
wrapper.cache_clear = cache_clear
return wrapper
return decorating_function

View File

@ -1,8 +0,0 @@
from __future__ import absolute_import
from imp import *
from .version_info import PY2
if PY2:
reload = reload

View File

@ -1,8 +0,0 @@
from __future__ import absolute_import
from itertools import *
from .version_info import PY2
if PY2:
filterfalse = ifilterfalse

View File

@ -1,244 +0,0 @@
"""pies/overrides.py.
Overrides Python syntax to conform to the Python3 version as much as possible using a '*' import
Copyright (C) 2013 Timothy Edmund Crosley
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
"""
from __future__ import absolute_import
import abc
import functools
import sys
from numbers import Integral
from ._utils import unmodified_isinstance, with_metaclass
from .version_info import PY2, PY3, VERSION
native_dict = dict
native_round = round
native_filter = filter
native_map = map
native_zip = zip
native_range = range
native_str = str
native_chr = chr
native_input = input
native_next = next
native_object = object
common = ['native_dict', 'native_round', 'native_filter', 'native_map', 'native_range', 'native_str', 'native_chr',
'native_input', 'PY2', 'PY3', 'u', 'itemsview', 'valuesview', 'keysview', 'execute', 'integer_types',
'native_next', 'native_object', 'with_metaclass']
if PY3:
import urllib
import builtins
from urllib import parse
from collections import OrderedDict
integer_types = (int, )
def u(string):
return string
def itemsview(collection):
return collection.items()
def valuesview(collection):
return collection.values()
def keysview(collection):
return collection.keys()
urllib.quote = parse.quote
urllib.quote_plus = parse.quote_plus
urllib.unquote = parse.unquote
urllib.unquote_plus = parse.unquote_plus
urllib.urlencode = parse.urlencode
execute = getattr(builtins, 'exec')
if VERSION[1] < 2:
def callable(entity):
return hasattr(entity, '__call__')
common.append('callable')
__all__ = common + ['OrderedDict', 'urllib']
else:
from itertools import ifilter as filter
from itertools import imap as map
from itertools import izip as zip
from decimal import Decimal, ROUND_HALF_EVEN
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
import codecs
str = unicode
chr = unichr
input = raw_input
range = xrange
integer_types = (int, long)
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
def _create_not_allowed(name):
def _not_allow(*args, **kwargs):
raise NameError("name '{0}' is not defined".format(name))
_not_allow.__name__ = name
return _not_allow
for removed in ('apply', 'cmp', 'coerce', 'execfile', 'raw_input', 'unpacks'):
globals()[removed] = _create_not_allowed(removed)
def u(s):
if isinstance(s, unicode):
return s
else:
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
def execute(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
class _dict_view_base(object):
__slots__ = ('_dictionary', )
def __init__(self, dictionary):
self._dictionary = dictionary
def __repr__(self):
return "{0}({1})".format(self.__class__.__name__, str(list(self.__iter__())))
def __unicode__(self):
return str(self.__repr__())
def __str__(self):
return str(self.__unicode__())
class dict_keys(_dict_view_base):
__slots__ = ()
def __iter__(self):
return self._dictionary.iterkeys()
class dict_values(_dict_view_base):
__slots__ = ()
def __iter__(self):
return self._dictionary.itervalues()
class dict_items(_dict_view_base):
__slots__ = ()
def __iter__(self):
return self._dictionary.iteritems()
def itemsview(collection):
return dict_items(collection)
def valuesview(collection):
return dict_values(collection)
def keysview(collection):
return dict_keys(collection)
class dict(unmodified_isinstance(native_dict)):
def has_key(self, *args, **kwargs):
return AttributeError("'dict' object has no attribute 'has_key'")
def items(self):
return dict_items(self)
def keys(self):
return dict_keys(self)
def values(self):
return dict_values(self)
def round(number, ndigits=None):
return_int = False
if ndigits is None:
return_int = True
ndigits = 0
if hasattr(number, '__round__'):
return number.__round__(ndigits)
if ndigits < 0:
raise NotImplementedError('negative ndigits not supported yet')
exponent = Decimal('10') ** (-ndigits)
d = Decimal.from_float(number).quantize(exponent,
rounding=ROUND_HALF_EVEN)
if return_int:
return int(d)
else:
return float(d)
def next(iterator):
try:
iterator.__next__()
except Exception:
native_next(iterator)
class FixStr(type):
def __new__(cls, name, bases, dct):
if '__str__' in dct:
dct['__unicode__'] = dct['__str__']
dct['__str__'] = lambda self: self.__unicode__().encode('utf-8')
return type.__new__(cls, name, bases, dct)
if sys.version_info[1] <= 6:
def __instancecheck__(cls, instance):
if cls.__name__ == "object":
return isinstance(instance, native_object)
subclass = getattr(instance, '__class__', None)
subtype = type(instance)
instance_type = getattr(abc, '_InstanceType', None)
if not instance_type:
class test_object:
pass
instance_type = type(test_object)
if subtype is instance_type:
subtype = subclass
if subtype is subclass or subclass is None:
return cls.__subclasscheck__(subtype)
return (cls.__subclasscheck__(subclass) or cls.__subclasscheck__(subtype))
else:
def __instancecheck__(cls, instance):
if cls.__name__ == "object":
return isinstance(instance, native_object)
return type.__instancecheck__(cls, instance)
class object(with_metaclass(FixStr, object)):
pass
__all__ = common + ['round', 'dict', 'apply', 'cmp', 'coerce', 'execfile', 'raw_input', 'unpacks', 'str', 'chr',
'input', 'range', 'filter', 'map', 'zip', 'object']

View File

@ -1,11 +0,0 @@
from __future__ import absolute_import
from .version_info import PY3
if PY3:
from pickle import *
else:
try:
from cPickle import *
except ImportError:
from pickle import *

View File

@ -1,6 +0,0 @@
from __future__ import absolute_import
from sys import *
if version_info[0] == 2:
intern = intern

View File

@ -1,17 +0,0 @@
from __future__ import absolute_import
import sys
from unittest import *
from ._utils import unmodified_isinstance
NativeTestCase = TestCase
if sys.version_info < (2, 7):
skip = lambda why: (lambda func: 'skip')
skipIf = lambda cond, why: (skip(why) if cond else lambda func: func)
class TestCase(unmodified_isinstance(TestCase)):
def assertIs(self, expr1, expr2, msg=None):
if expr1 is not expr2:
self.fail(msg or '%r is not %r' % (expr1, expr2))

View File

@ -1,7 +0,0 @@
from __future__ import absolute_import
import sys
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
VERSION = sys.version_info

View File

@ -0,0 +1,2 @@
__version__ = '0.8.1'

View File

@ -0,0 +1,5 @@
from pyflakes.api import main
# python -m pyflakes (with Python >= 2.7)
if __name__ == '__main__':
main(prog='pyflakes')

View File

@ -0,0 +1,133 @@
"""
API for the command-line I{pyflakes} tool.
"""
from __future__ import with_statement
import sys
import os
import _ast
from optparse import OptionParser
from pyflakes import checker, __version__
from pyflakes import reporter as modReporter
__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main']
def check(codeString, filename, reporter=None):
"""
Check the Python source given by C{codeString} for flakes.
@param codeString: The Python source to check.
@type codeString: C{str}
@param filename: The name of the file the source came from, used to report
errors.
@type filename: C{str}
@param reporter: A L{Reporter} instance, where errors and warnings will be
reported.
@return: The number of warnings emitted.
@rtype: C{int}
"""
if reporter is None:
reporter = modReporter._makeDefaultReporter()
# First, compile into an AST and handle syntax errors.
try:
tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
except SyntaxError:
value = sys.exc_info()[1]
msg = value.args[0]
(lineno, offset, text) = value.lineno, value.offset, value.text
# If there's an encoding problem with the file, the text is None.
if text is None:
# Avoid using msg, since for the only known case, it contains a
# bogus message that claims the encoding the file declared was
# unknown.
reporter.unexpectedError(filename, 'problem decoding source')
else:
reporter.syntaxError(filename, msg, lineno, offset, text)
return 1
except Exception:
reporter.unexpectedError(filename, 'problem decoding source')
return 1
# Okay, it's syntactically valid. Now check it.
w = checker.Checker(tree, filename)
w.messages.sort(key=lambda m: m.lineno)
for warning in w.messages:
reporter.flake(warning)
return len(w.messages)
def checkPath(filename, reporter=None):
"""
Check the given path, printing out any warnings detected.
@param reporter: A L{Reporter} instance, where errors and warnings will be
reported.
@return: the number of warnings printed
"""
if reporter is None:
reporter = modReporter._makeDefaultReporter()
try:
with open(filename, 'rb') as f:
codestr = f.read()
if sys.version_info < (2, 7):
codestr += '\n' # Work around for Python <= 2.6
except UnicodeError:
reporter.unexpectedError(filename, 'problem decoding source')
return 1
except IOError:
msg = sys.exc_info()[1]
reporter.unexpectedError(filename, msg.args[1])
return 1
return check(codestr, filename, reporter)
def iterSourceCode(paths):
"""
Iterate over all Python source files in C{paths}.
@param paths: A list of paths. Directories will be recursed into and
any .py files found will be yielded. Any non-directories will be
yielded as-is.
"""
for path in paths:
if os.path.isdir(path):
for dirpath, dirnames, filenames in os.walk(path):
for filename in filenames:
if filename.endswith('.py'):
yield os.path.join(dirpath, filename)
else:
yield path
def checkRecursive(paths, reporter):
"""
Recursively check all source files in C{paths}.
@param paths: A list of paths to Python source files and directories
containing Python source files.
@param reporter: A L{Reporter} where all of the warnings and errors
will be reported to.
@return: The number of warnings found.
"""
warnings = 0
for sourcePath in iterSourceCode(paths):
warnings += checkPath(sourcePath, reporter)
return warnings
def main(prog=None):
parser = OptionParser(prog=prog, version=__version__)
(__, args) = parser.parse_args()
reporter = modReporter._makeDefaultReporter()
if args:
warnings = checkRecursive(args, reporter)
else:
warnings = check(sys.stdin.read(), '<stdin>', reporter)
raise SystemExit(warnings > 0)

View File

@ -0,0 +1,870 @@
"""
Main module.
Implement the central Checker class.
Also, it models the Bindings and Scopes.
"""
import doctest
import os
import sys
PY2 = sys.version_info < (3, 0)
PY32 = sys.version_info < (3, 3) # Python 2.5 to 3.2
PY33 = sys.version_info < (3, 4) # Python 2.5 to 3.3
builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins'))
try:
import ast
except ImportError: # Python 2.5
import _ast as ast
if 'decorator_list' not in ast.ClassDef._fields:
# Patch the missing attribute 'decorator_list'
ast.ClassDef.decorator_list = ()
ast.FunctionDef.decorator_list = property(lambda s: s.decorators)
from pyflakes import messages
if PY2:
def getNodeType(node_class):
# workaround str.upper() which is locale-dependent
return str(unicode(node_class.__name__).upper())
else:
def getNodeType(node_class):
return node_class.__name__.upper()
# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally)
if PY32:
def getAlternatives(n):
if isinstance(n, (ast.If, ast.TryFinally)):
return [n.body]
if isinstance(n, ast.TryExcept):
return [n.body + n.orelse] + [[hdl] for hdl in n.handlers]
else:
def getAlternatives(n):
if isinstance(n, ast.If):
return [n.body]
if isinstance(n, ast.Try):
return [n.body + n.orelse] + [[hdl] for hdl in n.handlers]
class _FieldsOrder(dict):
"""Fix order of AST node fields."""
def _get_fields(self, node_class):
# handle iter before target, and generators before element
fields = node_class._fields
if 'iter' in fields:
key_first = 'iter'.find
elif 'generators' in fields:
key_first = 'generators'.find
else:
key_first = 'value'.find
return tuple(sorted(fields, key=key_first, reverse=True))
def __missing__(self, node_class):
self[node_class] = fields = self._get_fields(node_class)
return fields
def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()):
"""
Yield all direct child nodes of *node*, that is, all fields that
are nodes and all items of fields that are lists of nodes.
"""
for name in _fields_order[node.__class__]:
if name == omit:
continue
field = getattr(node, name, None)
if isinstance(field, ast.AST):
yield field
elif isinstance(field, list):
for item in field:
yield item
class Binding(object):
"""
Represents the binding of a value to a name.
The checker uses this to keep track of which names have been bound and
which names have not. See L{Assignment} for a special type of binding that
is checked with stricter rules.
@ivar used: pair of (L{Scope}, line-number) indicating the scope and
line number that this binding was last used
"""
def __init__(self, name, source):
self.name = name
self.source = source
self.used = False
def __str__(self):
return self.name
def __repr__(self):
return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
self.name,
self.source.lineno,
id(self))
def redefines(self, other):
return isinstance(other, Definition) and self.name == other.name
class Definition(Binding):
"""
A binding that defines a function or a class.
"""
class Importation(Definition):
"""
A binding created by an import statement.
@ivar fullName: The complete name given to the import statement,
possibly including multiple dotted components.
@type fullName: C{str}
"""
def __init__(self, name, source):
self.fullName = name
self.redefined = []
name = name.split('.')[0]
super(Importation, self).__init__(name, source)
def redefines(self, other):
if isinstance(other, Importation):
return self.fullName == other.fullName
return isinstance(other, Definition) and self.name == other.name
class Argument(Binding):
"""
Represents binding a name as an argument.
"""
class Assignment(Binding):
"""
Represents binding a name with an explicit assignment.
The checker will raise warnings for any Assignment that isn't used. Also,
the checker does not consider assignments in tuple/list unpacking to be
Assignments, rather it treats them as simple Bindings.
"""
class FunctionDefinition(Definition):
pass
class ClassDefinition(Definition):
pass
class ExportBinding(Binding):
"""
A binding created by an C{__all__} assignment. If the names in the list
can be determined statically, they will be treated as names for export and
additional checking applied to them.
The only C{__all__} assignment that can be recognized is one which takes
the value of a literal list containing literal strings. For example::
__all__ = ["foo", "bar"]
Names which are imported and not otherwise used but appear in the value of
C{__all__} will not have an unused import warning reported for them.
"""
def __init__(self, name, source, scope):
if '__all__' in scope and isinstance(source, ast.AugAssign):
self.names = list(scope['__all__'].names)
else:
self.names = []
if isinstance(source.value, (ast.List, ast.Tuple)):
for node in source.value.elts:
if isinstance(node, ast.Str):
self.names.append(node.s)
super(ExportBinding, self).__init__(name, source)
class Scope(dict):
importStarred = False # set to True when import * is found
def __repr__(self):
scope_cls = self.__class__.__name__
return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self))
class ClassScope(Scope):
pass
class FunctionScope(Scope):
"""
I represent a name scope for a function.
@ivar globals: Names declared 'global' in this function.
"""
usesLocals = False
alwaysUsed = set(['__tracebackhide__',
'__traceback_info__', '__traceback_supplement__'])
def __init__(self):
super(FunctionScope, self).__init__()
# Simplify: manage the special locals as globals
self.globals = self.alwaysUsed.copy()
self.returnValue = None # First non-empty return
self.isGenerator = False # Detect a generator
def unusedAssignments(self):
"""
Return a generator for the assignments which have not been used.
"""
for name, binding in self.items():
if (not binding.used and name not in self.globals
and not self.usesLocals
and isinstance(binding, Assignment)):
yield name, binding
class GeneratorScope(Scope):
pass
class ModuleScope(Scope):
pass
# Globally defined names which are not attributes of the builtins module, or
# are only present on some platforms.
_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError']
def getNodeName(node):
# Returns node.id, or node.name, or None
if hasattr(node, 'id'): # One of the many nodes with an id
return node.id
if hasattr(node, 'name'): # a ExceptHandler node
return node.name
class Checker(object):
"""
I check the cleanliness and sanity of Python code.
@ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements
of the list are two-tuples. The first element is the callable passed
to L{deferFunction}. The second element is a copy of the scope stack
at the time L{deferFunction} was called.
@ivar _deferredAssignments: Similar to C{_deferredFunctions}, but for
callables which are deferred assignment checks.
"""
nodeDepth = 0
offset = None
traceTree = False
builtIns = set(builtin_vars).union(_MAGIC_GLOBALS)
_customBuiltIns = os.environ.get('PYFLAKES_BUILTINS')
if _customBuiltIns:
builtIns.update(_customBuiltIns.split(','))
del _customBuiltIns
def __init__(self, tree, filename='(none)', builtins=None,
withDoctest='PYFLAKES_DOCTEST' in os.environ):
self._nodeHandlers = {}
self._deferredFunctions = []
self._deferredAssignments = []
self.deadScopes = []
self.messages = []
self.filename = filename
if builtins:
self.builtIns = self.builtIns.union(builtins)
self.withDoctest = withDoctest
self.scopeStack = [ModuleScope()]
self.exceptHandlers = [()]
self.futuresAllowed = True
self.root = tree
self.handleChildren(tree)
self.runDeferred(self._deferredFunctions)
# Set _deferredFunctions to None so that deferFunction will fail
# noisily if called after we've run through the deferred functions.
self._deferredFunctions = None
self.runDeferred(self._deferredAssignments)
# Set _deferredAssignments to None so that deferAssignment will fail
# noisily if called after we've run through the deferred assignments.
self._deferredAssignments = None
del self.scopeStack[1:]
self.popScope()
self.checkDeadScopes()
def deferFunction(self, callable):
"""
Schedule a function handler to be called just before completion.
This is used for handling function bodies, which must be deferred
because code later in the file might modify the global scope. When
`callable` is called, the scope at the time this is called will be
restored, however it will contain any new bindings added to it.
"""
self._deferredFunctions.append((callable, self.scopeStack[:], self.offset))
def deferAssignment(self, callable):
"""
Schedule an assignment handler to be called just after deferred
function handlers.
"""
self._deferredAssignments.append((callable, self.scopeStack[:], self.offset))
def runDeferred(self, deferred):
"""
Run the callables in C{deferred} using their associated scope stack.
"""
for handler, scope, offset in deferred:
self.scopeStack = scope
self.offset = offset
handler()
@property
def scope(self):
return self.scopeStack[-1]
def popScope(self):
self.deadScopes.append(self.scopeStack.pop())
def checkDeadScopes(self):
"""
Look at scopes which have been fully examined and report names in them
which were imported but unused.
"""
for scope in self.deadScopes:
if isinstance(scope.get('__all__'), ExportBinding):
all_names = set(scope['__all__'].names)
if not scope.importStarred and \
os.path.basename(self.filename) != '__init__.py':
# Look for possible mistakes in the export list
undefined = all_names.difference(scope)
for name in undefined:
self.report(messages.UndefinedExport,
scope['__all__'].source, name)
else:
all_names = []
# Look for imported names that aren't used.
for value in scope.values():
if isinstance(value, Importation):
used = value.used or value.name in all_names
if not used:
messg = messages.UnusedImport
self.report(messg, value.source, value.name)
for node in value.redefined:
if isinstance(self.getParent(node), ast.For):
messg = messages.ImportShadowedByLoopVar
elif used:
continue
else:
messg = messages.RedefinedWhileUnused
self.report(messg, node, value.name, value.source)
def pushScope(self, scopeClass=FunctionScope):
self.scopeStack.append(scopeClass())
def report(self, messageClass, *args, **kwargs):
self.messages.append(messageClass(self.filename, *args, **kwargs))
def getParent(self, node):
# Lookup the first parent which is not Tuple, List or Starred
while True:
node = node.parent
if not hasattr(node, 'elts') and not hasattr(node, 'ctx'):
return node
def getCommonAncestor(self, lnode, rnode, stop):
if stop in (lnode, rnode) or not (hasattr(lnode, 'parent') and
hasattr(rnode, 'parent')):
return None
if lnode is rnode:
return lnode
if (lnode.depth > rnode.depth):
return self.getCommonAncestor(lnode.parent, rnode, stop)
if (lnode.depth < rnode.depth):
return self.getCommonAncestor(lnode, rnode.parent, stop)
return self.getCommonAncestor(lnode.parent, rnode.parent, stop)
def descendantOf(self, node, ancestors, stop):
for a in ancestors:
if self.getCommonAncestor(node, a, stop):
return True
return False
def differentForks(self, lnode, rnode):
"""True, if lnode and rnode are located on different forks of IF/TRY"""
ancestor = self.getCommonAncestor(lnode, rnode, self.root)
parts = getAlternatives(ancestor)
if parts:
for items in parts:
if self.descendantOf(lnode, items, ancestor) ^ \
self.descendantOf(rnode, items, ancestor):
return True
return False
def addBinding(self, node, value):
"""
Called when a binding is altered.
- `node` is the statement responsible for the change
- `value` is the new value, a Binding instance
"""
# assert value.source in (node, node.parent):
for scope in self.scopeStack[::-1]:
if value.name in scope:
break
existing = scope.get(value.name)
if existing and not self.differentForks(node, existing.source):
parent_stmt = self.getParent(value.source)
if isinstance(existing, Importation) and isinstance(parent_stmt, ast.For):
self.report(messages.ImportShadowedByLoopVar,
node, value.name, existing.source)
elif scope is self.scope:
if (isinstance(parent_stmt, ast.comprehension) and
not isinstance(self.getParent(existing.source),
(ast.For, ast.comprehension))):
self.report(messages.RedefinedInListComp,
node, value.name, existing.source)
elif not existing.used and value.redefines(existing):
self.report(messages.RedefinedWhileUnused,
node, value.name, existing.source)
elif isinstance(existing, Importation) and value.redefines(existing):
existing.redefined.append(node)
self.scope[value.name] = value
def getNodeHandler(self, node_class):
try:
return self._nodeHandlers[node_class]
except KeyError:
nodeType = getNodeType(node_class)
self._nodeHandlers[node_class] = handler = getattr(self, nodeType)
return handler
def handleNodeLoad(self, node):
name = getNodeName(node)
if not name:
return
# try local scope
try:
self.scope[name].used = (self.scope, node)
except KeyError:
pass
else:
return
scopes = [scope for scope in self.scopeStack[:-1]
if isinstance(scope, (FunctionScope, ModuleScope))]
if isinstance(self.scope, GeneratorScope) and scopes[-1] != self.scopeStack[-2]:
scopes.append(self.scopeStack[-2])
# try enclosing function scopes and global scope
importStarred = self.scope.importStarred
for scope in reversed(scopes):
importStarred = importStarred or scope.importStarred
try:
scope[name].used = (self.scope, node)
except KeyError:
pass
else:
return
# look in the built-ins
if importStarred or name in self.builtIns:
return
if name == '__path__' and os.path.basename(self.filename) == '__init__.py':
# the special name __path__ is valid only in packages
return
# protected with a NameError handler?
if 'NameError' not in self.exceptHandlers[-1]:
self.report(messages.UndefinedName, node, name)
def handleNodeStore(self, node):
name = getNodeName(node)
if not name:
return
# if the name hasn't already been defined in the current scope
if isinstance(self.scope, FunctionScope) and name not in self.scope:
# for each function or module scope above us
for scope in self.scopeStack[:-1]:
if not isinstance(scope, (FunctionScope, ModuleScope)):
continue
# if the name was defined in that scope, and the name has
# been accessed already in the current scope, and hasn't
# been declared global
used = name in scope and scope[name].used
if used and used[0] is self.scope and name not in self.scope.globals:
# then it's probably a mistake
self.report(messages.UndefinedLocal,
scope[name].used[1], name, scope[name].source)
break
parent_stmt = self.getParent(node)
if isinstance(parent_stmt, (ast.For, ast.comprehension)) or (
parent_stmt != node.parent and
not self.isLiteralTupleUnpacking(parent_stmt)):
binding = Binding(name, node)
elif name == '__all__' and isinstance(self.scope, ModuleScope):
binding = ExportBinding(name, node.parent, self.scope)
else:
binding = Assignment(name, node)
if name in self.scope:
binding.used = self.scope[name].used
self.addBinding(node, binding)
def handleNodeDelete(self, node):
name = getNodeName(node)
if not name:
return
if isinstance(self.scope, FunctionScope) and name in self.scope.globals:
self.scope.globals.remove(name)
else:
try:
del self.scope[name]
except KeyError:
self.report(messages.UndefinedName, node, name)
def handleChildren(self, tree, omit=None):
for node in iter_child_nodes(tree, omit=omit):
self.handleNode(node, tree)
def isLiteralTupleUnpacking(self, node):
if isinstance(node, ast.Assign):
for child in node.targets + [node.value]:
if not hasattr(child, 'elts'):
return False
return True
def isDocstring(self, node):
"""
Determine if the given node is a docstring, as long as it is at the
correct place in the node tree.
"""
return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and
isinstance(node.value, ast.Str))
def getDocstring(self, node):
if isinstance(node, ast.Expr):
node = node.value
if not isinstance(node, ast.Str):
return (None, None)
# Computed incorrectly if the docstring has backslash
doctest_lineno = node.lineno - node.s.count('\n') - 1
return (node.s, doctest_lineno)
def handleNode(self, node, parent):
if node is None:
return
if self.offset and getattr(node, 'lineno', None) is not None:
node.lineno += self.offset[0]
node.col_offset += self.offset[1]
if self.traceTree:
print(' ' * self.nodeDepth + node.__class__.__name__)
if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or
self.isDocstring(node)):
self.futuresAllowed = False
self.nodeDepth += 1
node.depth = self.nodeDepth
node.parent = parent
try:
handler = self.getNodeHandler(node.__class__)
handler(node)
finally:
self.nodeDepth -= 1
if self.traceTree:
print(' ' * self.nodeDepth + 'end ' + node.__class__.__name__)
_getDoctestExamples = doctest.DocTestParser().get_examples
def handleDoctests(self, node):
try:
(docstring, node_lineno) = self.getDocstring(node.body[0])
examples = docstring and self._getDoctestExamples(docstring)
except (ValueError, IndexError):
# e.g. line 6 of the docstring for <string> has inconsistent
# leading whitespace: ...
return
if not examples:
return
node_offset = self.offset or (0, 0)
self.pushScope()
underscore_in_builtins = '_' in self.builtIns
if not underscore_in_builtins:
self.builtIns.add('_')
for example in examples:
try:
tree = compile(example.source, "<doctest>", "exec", ast.PyCF_ONLY_AST)
except SyntaxError:
e = sys.exc_info()[1]
position = (node_lineno + example.lineno + e.lineno,
example.indent + 4 + (e.offset or 0))
self.report(messages.DoctestSyntaxError, node, position)
else:
self.offset = (node_offset[0] + node_lineno + example.lineno,
node_offset[1] + example.indent + 4)
self.handleChildren(tree)
self.offset = node_offset
if not underscore_in_builtins:
self.builtIns.remove('_')
self.popScope()
def ignore(self, node):
pass
# "stmt" type nodes
DELETE = PRINT = FOR = WHILE = IF = WITH = WITHITEM = RAISE = \
TRYFINALLY = ASSERT = EXEC = EXPR = ASSIGN = handleChildren
CONTINUE = BREAK = PASS = ignore
# "expr" type nodes
BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = \
COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = \
STARRED = NAMECONSTANT = handleChildren
NUM = STR = BYTES = ELLIPSIS = ignore
# "slice" type nodes
SLICE = EXTSLICE = INDEX = handleChildren
# expression contexts are node instances too, though being constants
LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore
# same for operators
AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \
BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \
EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
# additional node types
LISTCOMP = COMPREHENSION = KEYWORD = handleChildren
def GLOBAL(self, node):
"""
Keep track of globals declarations.
"""
if isinstance(self.scope, FunctionScope):
self.scope.globals.update(node.names)
NONLOCAL = GLOBAL
def GENERATOREXP(self, node):
self.pushScope(GeneratorScope)
self.handleChildren(node)
self.popScope()
DICTCOMP = SETCOMP = GENERATOREXP
def NAME(self, node):
"""
Handle occurrence of Name (which can be a load/store/delete access.)
"""
# Locate the name in locals / function / globals scopes.
if isinstance(node.ctx, (ast.Load, ast.AugLoad)):
self.handleNodeLoad(node)
if (node.id == 'locals' and isinstance(self.scope, FunctionScope)
and isinstance(node.parent, ast.Call)):
# we are doing locals() call in current scope
self.scope.usesLocals = True
elif isinstance(node.ctx, (ast.Store, ast.AugStore)):
self.handleNodeStore(node)
elif isinstance(node.ctx, ast.Del):
self.handleNodeDelete(node)
else:
# must be a Param context -- this only happens for names in function
# arguments, but these aren't dispatched through here
raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
def RETURN(self, node):
if node.value and not self.scope.returnValue:
self.scope.returnValue = node.value
self.handleNode(node.value, node)
def YIELD(self, node):
self.scope.isGenerator = True
self.handleNode(node.value, node)
YIELDFROM = YIELD
def FUNCTIONDEF(self, node):
for deco in node.decorator_list:
self.handleNode(deco, node)
self.LAMBDA(node)
self.addBinding(node, FunctionDefinition(node.name, node))
if self.withDoctest:
self.deferFunction(lambda: self.handleDoctests(node))
def LAMBDA(self, node):
args = []
annotations = []
if PY2:
def addArgs(arglist):
for arg in arglist:
if isinstance(arg, ast.Tuple):
addArgs(arg.elts)
else:
args.append(arg.id)
addArgs(node.args.args)
defaults = node.args.defaults
else:
for arg in node.args.args + node.args.kwonlyargs:
args.append(arg.arg)
annotations.append(arg.annotation)
defaults = node.args.defaults + node.args.kw_defaults
# Only for Python3 FunctionDefs
is_py3_func = hasattr(node, 'returns')
for arg_name in ('vararg', 'kwarg'):
wildcard = getattr(node.args, arg_name)
if not wildcard:
continue
args.append(wildcard if PY33 else wildcard.arg)
if is_py3_func:
if PY33: # Python 2.5 to 3.3
argannotation = arg_name + 'annotation'
annotations.append(getattr(node.args, argannotation))
else: # Python >= 3.4
annotations.append(wildcard.annotation)
if is_py3_func:
annotations.append(node.returns)
if len(set(args)) < len(args):
for (idx, arg) in enumerate(args):
if arg in args[:idx]:
self.report(messages.DuplicateArgument, node, arg)
for child in annotations + defaults:
if child:
self.handleNode(child, node)
def runFunction():
self.pushScope()
for name in args:
self.addBinding(node, Argument(name, node))
if isinstance(node.body, list):
# case for FunctionDefs
for stmt in node.body:
self.handleNode(stmt, node)
else:
# case for Lambdas
self.handleNode(node.body, node)
def checkUnusedAssignments():
"""
Check to see if any assignments have not been used.
"""
for name, binding in self.scope.unusedAssignments():
self.report(messages.UnusedVariable, binding.source, name)
self.deferAssignment(checkUnusedAssignments)
if PY32:
def checkReturnWithArgumentInsideGenerator():
"""
Check to see if there is any return statement with
arguments but the function is a generator.
"""
if self.scope.isGenerator and self.scope.returnValue:
self.report(messages.ReturnWithArgsInsideGenerator,
self.scope.returnValue)
self.deferAssignment(checkReturnWithArgumentInsideGenerator)
self.popScope()
self.deferFunction(runFunction)
def CLASSDEF(self, node):
"""
Check names used in a class definition, including its decorators, base
classes, and the body of its definition. Additionally, add its name to
the current scope.
"""
for deco in node.decorator_list:
self.handleNode(deco, node)
for baseNode in node.bases:
self.handleNode(baseNode, node)
if not PY2:
for keywordNode in node.keywords:
self.handleNode(keywordNode, node)
self.pushScope(ClassScope)
if self.withDoctest:
self.deferFunction(lambda: self.handleDoctests(node))
for stmt in node.body:
self.handleNode(stmt, node)
self.popScope()
self.addBinding(node, ClassDefinition(node.name, node))
def AUGASSIGN(self, node):
self.handleNodeLoad(node.target)
self.handleNode(node.value, node)
self.handleNode(node.target, node)
def IMPORT(self, node):
for alias in node.names:
name = alias.asname or alias.name
importation = Importation(name, node)
self.addBinding(node, importation)
def IMPORTFROM(self, node):
if node.module == '__future__':
if not self.futuresAllowed:
self.report(messages.LateFutureImport,
node, [n.name for n in node.names])
else:
self.futuresAllowed = False
for alias in node.names:
if alias.name == '*':
self.scope.importStarred = True
self.report(messages.ImportStarUsed, node, node.module)
continue
name = alias.asname or alias.name
importation = Importation(name, node)
if node.module == '__future__':
importation.used = (self.scope, node)
self.addBinding(node, importation)
def TRY(self, node):
handler_names = []
# List the exception handlers
for handler in node.handlers:
if isinstance(handler.type, ast.Tuple):
for exc_type in handler.type.elts:
handler_names.append(getNodeName(exc_type))
elif handler.type:
handler_names.append(getNodeName(handler.type))
# Memorize the except handlers and process the body
self.exceptHandlers.append(handler_names)
for child in node.body:
self.handleNode(child, node)
self.exceptHandlers.pop()
# Process the other nodes: "except:", "else:", "finally:"
self.handleChildren(node, omit='body')
TRYEXCEPT = TRY
def EXCEPTHANDLER(self, node):
# 3.x: in addition to handling children, we must handle the name of
# the exception, which is not a Name node, but a simple string.
if isinstance(node.name, str):
self.handleNodeStore(node)
self.handleChildren(node)

View File

@ -0,0 +1,135 @@
"""
Provide the class Message and its subclasses.
"""
class Message(object):
message = ''
message_args = ()
def __init__(self, filename, loc):
self.filename = filename
self.lineno = loc.lineno
self.col = getattr(loc, 'col_offset', 0)
def __str__(self):
return '%s:%s: %s' % (self.filename, self.lineno,
self.message % self.message_args)
class UnusedImport(Message):
message = '%r imported but unused'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class RedefinedWhileUnused(Message):
message = 'redefinition of unused %r from line %r'
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
self.message_args = (name, orig_loc.lineno)
class RedefinedInListComp(Message):
message = 'list comprehension redefines %r from line %r'
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
self.message_args = (name, orig_loc.lineno)
class ImportShadowedByLoopVar(Message):
message = 'import %r from line %r shadowed by loop variable'
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
self.message_args = (name, orig_loc.lineno)
class ImportStarUsed(Message):
message = "'from %s import *' used; unable to detect undefined names"
def __init__(self, filename, loc, modname):
Message.__init__(self, filename, loc)
self.message_args = (modname,)
class UndefinedName(Message):
message = 'undefined name %r'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class DoctestSyntaxError(Message):
message = 'syntax error in doctest'
def __init__(self, filename, loc, position=None):
Message.__init__(self, filename, loc)
if position:
(self.lineno, self.col) = position
self.message_args = ()
class UndefinedExport(Message):
message = 'undefined name %r in __all__'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class UndefinedLocal(Message):
message = ('local variable %r (defined in enclosing scope on line %r) '
'referenced before assignment')
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
self.message_args = (name, orig_loc.lineno)
class DuplicateArgument(Message):
message = 'duplicate argument %r in function definition'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class Redefined(Message):
message = 'redefinition of %r from line %r'
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
self.message_args = (name, orig_loc.lineno)
class LateFutureImport(Message):
message = 'future import(s) %r after other statements'
def __init__(self, filename, loc, names):
Message.__init__(self, filename, loc)
self.message_args = (names,)
class UnusedVariable(Message):
"""
Indicates that a variable has been explicity assigned to but not actually
used.
"""
message = 'local variable %r is assigned to but never used'
def __init__(self, filename, loc, names):
Message.__init__(self, filename, loc)
self.message_args = (names,)
class ReturnWithArgsInsideGenerator(Message):
"""
Indicates a return statement with arguments inside a generator.
"""
message = '\'return\' with argument inside generator'

View File

@ -0,0 +1,81 @@
"""
Provide the Reporter class.
"""
import re
import sys
class Reporter(object):
"""
Formats the results of pyflakes checks to users.
"""
def __init__(self, warningStream, errorStream):
"""
Construct a L{Reporter}.
@param warningStream: A file-like object where warnings will be
written to. The stream's C{write} method must accept unicode.
C{sys.stdout} is a good value.
@param errorStream: A file-like object where error output will be
written to. The stream's C{write} method must accept unicode.
C{sys.stderr} is a good value.
"""
self._stdout = warningStream
self._stderr = errorStream
def unexpectedError(self, filename, msg):
"""
An unexpected error occurred trying to process C{filename}.
@param filename: The path to a file that we could not process.
@ptype filename: C{unicode}
@param msg: A message explaining the problem.
@ptype msg: C{unicode}
"""
self._stderr.write("%s: %s\n" % (filename, msg))
def syntaxError(self, filename, msg, lineno, offset, text):
"""
There was a syntax errror in C{filename}.
@param filename: The path to the file with the syntax error.
@ptype filename: C{unicode}
@param msg: An explanation of the syntax error.
@ptype msg: C{unicode}
@param lineno: The line number where the syntax error occurred.
@ptype lineno: C{int}
@param offset: The column on which the syntax error occurred, or None.
@ptype offset: C{int}
@param text: The source code containing the syntax error.
@ptype text: C{unicode}
"""
line = text.splitlines()[-1]
if offset is not None:
offset = offset - (len(text) - len(line))
self._stderr.write('%s:%d:%d: %s\n' %
(filename, lineno, offset + 1, msg))
else:
self._stderr.write('%s:%d: %s\n' % (filename, lineno, msg))
self._stderr.write(line)
self._stderr.write('\n')
if offset is not None:
self._stderr.write(re.sub(r'\S', ' ', line[:offset]) +
"^\n")
def flake(self, message):
"""
pyflakes found something wrong with the code.
@param: A L{pyflakes.messages.Message}.
"""
self._stdout.write(str(message))
self._stdout.write('\n')
def _makeDefaultReporter():
"""
Make a reporter that can be used when no reporter is specified.
"""
return Reporter(sys.stdout, sys.stderr)

View File

@ -0,0 +1,7 @@
"""
Implementation of the command-line I{pyflakes} tool.
"""
from __future__ import absolute_import
# For backward compatibility
from pyflakes.api import check, checkPath, checkRecursive, iterSourceCode, main

View File

View File

@ -0,0 +1,43 @@
import sys
import textwrap
import unittest
from pyflakes import checker
__all__ = ['TestCase', 'skip', 'skipIf']
if sys.version_info < (2, 7):
skip = lambda why: (lambda func: 'skip') # not callable
skipIf = lambda cond, why: (skip(why) if cond else lambda func: func)
else:
skip = unittest.skip
skipIf = unittest.skipIf
PyCF_ONLY_AST = 1024
class TestCase(unittest.TestCase):
withDoctest = False
def flakes(self, input, *expectedOutputs, **kw):
tree = compile(textwrap.dedent(input), "<test>", "exec", PyCF_ONLY_AST)
w = checker.Checker(tree, withDoctest=self.withDoctest, **kw)
outputs = [type(o) for o in w.messages]
expectedOutputs = list(expectedOutputs)
outputs.sort(key=lambda t: t.__name__)
expectedOutputs.sort(key=lambda t: t.__name__)
self.assertEqual(outputs, expectedOutputs, '''\
for input:
%s
expected outputs:
%r
but got:
%s''' % (input, expectedOutputs, '\n'.join([str(o) for o in w.messages])))
return w
if sys.version_info < (2, 7):
def assertIs(self, expr1, expr2, msg=None):
if expr1 is not expr2:
self.fail(msg or '%r is not %r' % (expr1, expr2))

View File

@ -0,0 +1,591 @@
"""
Tests for L{pyflakes.scripts.pyflakes}.
"""
import os
import sys
import shutil
import subprocess
import tempfile
from pyflakes.messages import UnusedImport
from pyflakes.reporter import Reporter
from pyflakes.api import (
checkPath,
checkRecursive,
iterSourceCode,
)
from pyflakes.test.harness import TestCase
if sys.version_info < (3,):
from cStringIO import StringIO
else:
from io import StringIO
unichr = chr
def withStderrTo(stderr, f, *args, **kwargs):
"""
Call C{f} with C{sys.stderr} redirected to C{stderr}.
"""
(outer, sys.stderr) = (sys.stderr, stderr)
try:
return f(*args, **kwargs)
finally:
sys.stderr = outer
class Node(object):
"""
Mock an AST node.
"""
def __init__(self, lineno, col_offset=0):
self.lineno = lineno
self.col_offset = col_offset
class LoggingReporter(object):
"""
Implementation of Reporter that just appends any error to a list.
"""
def __init__(self, log):
"""
Construct a C{LoggingReporter}.
@param log: A list to append log messages to.
"""
self.log = log
def flake(self, message):
self.log.append(('flake', str(message)))
def unexpectedError(self, filename, message):
self.log.append(('unexpectedError', filename, message))
def syntaxError(self, filename, msg, lineno, offset, line):
self.log.append(('syntaxError', filename, msg, lineno, offset, line))
class TestIterSourceCode(TestCase):
"""
Tests for L{iterSourceCode}.
"""
def setUp(self):
self.tempdir = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self.tempdir)
def makeEmptyFile(self, *parts):
assert parts
fpath = os.path.join(self.tempdir, *parts)
fd = open(fpath, 'a')
fd.close()
return fpath
def test_emptyDirectory(self):
"""
There are no Python files in an empty directory.
"""
self.assertEqual(list(iterSourceCode([self.tempdir])), [])
def test_singleFile(self):
"""
If the directory contains one Python file, C{iterSourceCode} will find
it.
"""
childpath = self.makeEmptyFile('foo.py')
self.assertEqual(list(iterSourceCode([self.tempdir])), [childpath])
def test_onlyPythonSource(self):
"""
Files that are not Python source files are not included.
"""
self.makeEmptyFile('foo.pyc')
self.assertEqual(list(iterSourceCode([self.tempdir])), [])
def test_recurses(self):
"""
If the Python files are hidden deep down in child directories, we will
find them.
"""
os.mkdir(os.path.join(self.tempdir, 'foo'))
apath = self.makeEmptyFile('foo', 'a.py')
os.mkdir(os.path.join(self.tempdir, 'bar'))
bpath = self.makeEmptyFile('bar', 'b.py')
cpath = self.makeEmptyFile('c.py')
self.assertEqual(
sorted(iterSourceCode([self.tempdir])),
sorted([apath, bpath, cpath]))
def test_multipleDirectories(self):
"""
L{iterSourceCode} can be given multiple directories. It will recurse
into each of them.
"""
foopath = os.path.join(self.tempdir, 'foo')
barpath = os.path.join(self.tempdir, 'bar')
os.mkdir(foopath)
apath = self.makeEmptyFile('foo', 'a.py')
os.mkdir(barpath)
bpath = self.makeEmptyFile('bar', 'b.py')
self.assertEqual(
sorted(iterSourceCode([foopath, barpath])),
sorted([apath, bpath]))
def test_explicitFiles(self):
"""
If one of the paths given to L{iterSourceCode} is not a directory but
a file, it will include that in its output.
"""
epath = self.makeEmptyFile('e.py')
self.assertEqual(list(iterSourceCode([epath])),
[epath])
class TestReporter(TestCase):
"""
Tests for L{Reporter}.
"""
def test_syntaxError(self):
"""
C{syntaxError} reports that there was a syntax error in the source
file. It reports to the error stream and includes the filename, line
number, error message, actual line of source and a caret pointing to
where the error is.
"""
err = StringIO()
reporter = Reporter(None, err)
reporter.syntaxError('foo.py', 'a problem', 3, 7, 'bad line of source')
self.assertEqual(
("foo.py:3:8: a problem\n"
"bad line of source\n"
" ^\n"),
err.getvalue())
def test_syntaxErrorNoOffset(self):
"""
C{syntaxError} doesn't include a caret pointing to the error if
C{offset} is passed as C{None}.
"""
err = StringIO()
reporter = Reporter(None, err)
reporter.syntaxError('foo.py', 'a problem', 3, None,
'bad line of source')
self.assertEqual(
("foo.py:3: a problem\n"
"bad line of source\n"),
err.getvalue())
def test_multiLineSyntaxError(self):
"""
If there's a multi-line syntax error, then we only report the last
line. The offset is adjusted so that it is relative to the start of
the last line.
"""
err = StringIO()
lines = [
'bad line of source',
'more bad lines of source',
]
reporter = Reporter(None, err)
reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7,
'\n'.join(lines))
self.assertEqual(
("foo.py:3:7: a problem\n" +
lines[-1] + "\n" +
" ^\n"),
err.getvalue())
def test_unexpectedError(self):
"""
C{unexpectedError} reports an error processing a source file.
"""
err = StringIO()
reporter = Reporter(None, err)
reporter.unexpectedError('source.py', 'error message')
self.assertEqual('source.py: error message\n', err.getvalue())
def test_flake(self):
"""
C{flake} reports a code warning from Pyflakes. It is exactly the
str() of a L{pyflakes.messages.Message}.
"""
out = StringIO()
reporter = Reporter(out, None)
message = UnusedImport('foo.py', Node(42), 'bar')
reporter.flake(message)
self.assertEqual(out.getvalue(), "%s\n" % (message,))
class CheckTests(TestCase):
"""
Tests for L{check} and L{checkPath} which check a file for flakes.
"""
def makeTempFile(self, content):
"""
Make a temporary file containing C{content} and return a path to it.
"""
_, fpath = tempfile.mkstemp()
if not hasattr(content, 'decode'):
content = content.encode('ascii')
fd = open(fpath, 'wb')
fd.write(content)
fd.close()
return fpath
def assertHasErrors(self, path, errorList):
"""
Assert that C{path} causes errors.
@param path: A path to a file to check.
@param errorList: A list of errors expected to be printed to stderr.
"""
err = StringIO()
count = withStderrTo(err, checkPath, path)
self.assertEqual(
(count, err.getvalue()), (len(errorList), ''.join(errorList)))
def getErrors(self, path):
"""
Get any warnings or errors reported by pyflakes for the file at C{path}.
@param path: The path to a Python file on disk that pyflakes will check.
@return: C{(count, log)}, where C{count} is the number of warnings or
errors generated, and log is a list of those warnings, presented
as structured data. See L{LoggingReporter} for more details.
"""
log = []
reporter = LoggingReporter(log)
count = checkPath(path, reporter)
return count, log
def test_legacyScript(self):
from pyflakes.scripts import pyflakes as script_pyflakes
self.assertIs(script_pyflakes.checkPath, checkPath)
def test_missingTrailingNewline(self):
"""
Source which doesn't end with a newline shouldn't cause any
exception to be raised nor an error indicator to be returned by
L{check}.
"""
fName = self.makeTempFile("def foo():\n\tpass\n\t")
self.assertHasErrors(fName, [])
def test_checkPathNonExisting(self):
"""
L{checkPath} handles non-existing files.
"""
count, errors = self.getErrors('extremo')
self.assertEqual(count, 1)
self.assertEqual(
errors,
[('unexpectedError', 'extremo', 'No such file or directory')])
def test_multilineSyntaxError(self):
"""
Source which includes a syntax error which results in the raised
L{SyntaxError.text} containing multiple lines of source are reported
with only the last line of that source.
"""
source = """\
def foo():
'''
def bar():
pass
def baz():
'''quux'''
"""
# Sanity check - SyntaxError.text should be multiple lines, if it
# isn't, something this test was unprepared for has happened.
def evaluate(source):
exec(source)
try:
evaluate(source)
except SyntaxError:
e = sys.exc_info()[1]
self.assertTrue(e.text.count('\n') > 1)
else:
self.fail()
sourcePath = self.makeTempFile(source)
self.assertHasErrors(
sourcePath,
["""\
%s:8:11: invalid syntax
'''quux'''
^
""" % (sourcePath,)])
def test_eofSyntaxError(self):
"""
The error reported for source files which end prematurely causing a
syntax error reflects the cause for the syntax error.
"""
sourcePath = self.makeTempFile("def foo(")
self.assertHasErrors(
sourcePath,
["""\
%s:1:9: unexpected EOF while parsing
def foo(
^
""" % (sourcePath,)])
def test_eofSyntaxErrorWithTab(self):
"""
The error reported for source files which end prematurely causing a
syntax error reflects the cause for the syntax error.
"""
sourcePath = self.makeTempFile("if True:\n\tfoo =")
self.assertHasErrors(
sourcePath,
["""\
%s:2:7: invalid syntax
\tfoo =
\t ^
""" % (sourcePath,)])
def test_nonDefaultFollowsDefaultSyntaxError(self):
"""
Source which has a non-default argument following a default argument
should include the line number of the syntax error. However these
exceptions do not include an offset.
"""
source = """\
def foo(bar=baz, bax):
pass
"""
sourcePath = self.makeTempFile(source)
last_line = ' ^\n' if sys.version_info >= (3, 2) else ''
column = '8:' if sys.version_info >= (3, 2) else ''
self.assertHasErrors(
sourcePath,
["""\
%s:1:%s non-default argument follows default argument
def foo(bar=baz, bax):
%s""" % (sourcePath, column, last_line)])
def test_nonKeywordAfterKeywordSyntaxError(self):
"""
Source which has a non-keyword argument after a keyword argument should
include the line number of the syntax error. However these exceptions
do not include an offset.
"""
source = """\
foo(bar=baz, bax)
"""
sourcePath = self.makeTempFile(source)
last_line = ' ^\n' if sys.version_info >= (3, 2) else ''
column = '13:' if sys.version_info >= (3, 2) else ''
self.assertHasErrors(
sourcePath,
["""\
%s:1:%s non-keyword arg after keyword arg
foo(bar=baz, bax)
%s""" % (sourcePath, column, last_line)])
def test_invalidEscape(self):
"""
The invalid escape syntax raises ValueError in Python 2
"""
ver = sys.version_info
# ValueError: invalid \x escape
sourcePath = self.makeTempFile(r"foo = '\xyz'")
if ver < (3,):
decoding_error = "%s: problem decoding source\n" % (sourcePath,)
else:
last_line = ' ^\n' if ver >= (3, 2) else ''
# Column has been "fixed" since 3.2.4 and 3.3.1
col = 1 if ver >= (3, 3, 1) or ((3, 2, 4) <= ver < (3, 3)) else 2
decoding_error = """\
%s:1:7: (unicode error) 'unicodeescape' codec can't decode bytes \
in position 0-%d: truncated \\xXX escape
foo = '\\xyz'
%s""" % (sourcePath, col, last_line)
self.assertHasErrors(
sourcePath, [decoding_error])
def test_permissionDenied(self):
"""
If the source file is not readable, this is reported on standard
error.
"""
sourcePath = self.makeTempFile('')
os.chmod(sourcePath, 0)
count, errors = self.getErrors(sourcePath)
self.assertEqual(count, 1)
self.assertEqual(
errors,
[('unexpectedError', sourcePath, "Permission denied")])
def test_pyflakesWarning(self):
"""
If the source file has a pyflakes warning, this is reported as a
'flake'.
"""
sourcePath = self.makeTempFile("import foo")
count, errors = self.getErrors(sourcePath)
self.assertEqual(count, 1)
self.assertEqual(
errors, [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))])
def test_encodedFileUTF8(self):
"""
If source file declares the correct encoding, no error is reported.
"""
SNOWMAN = unichr(0x2603)
source = ("""\
# coding: utf-8
x = "%s"
""" % SNOWMAN).encode('utf-8')
sourcePath = self.makeTempFile(source)
self.assertHasErrors(sourcePath, [])
def test_misencodedFileUTF8(self):
"""
If a source file contains bytes which cannot be decoded, this is
reported on stderr.
"""
SNOWMAN = unichr(0x2603)
source = ("""\
# coding: ascii
x = "%s"
""" % SNOWMAN).encode('utf-8')
sourcePath = self.makeTempFile(source)
self.assertHasErrors(
sourcePath, ["%s: problem decoding source\n" % (sourcePath,)])
def test_misencodedFileUTF16(self):
"""
If a source file contains bytes which cannot be decoded, this is
reported on stderr.
"""
SNOWMAN = unichr(0x2603)
source = ("""\
# coding: ascii
x = "%s"
""" % SNOWMAN).encode('utf-16')
sourcePath = self.makeTempFile(source)
self.assertHasErrors(
sourcePath, ["%s: problem decoding source\n" % (sourcePath,)])
def test_checkRecursive(self):
"""
L{checkRecursive} descends into each directory, finding Python files
and reporting problems.
"""
tempdir = tempfile.mkdtemp()
os.mkdir(os.path.join(tempdir, 'foo'))
file1 = os.path.join(tempdir, 'foo', 'bar.py')
fd = open(file1, 'wb')
fd.write("import baz\n".encode('ascii'))
fd.close()
file2 = os.path.join(tempdir, 'baz.py')
fd = open(file2, 'wb')
fd.write("import contraband".encode('ascii'))
fd.close()
log = []
reporter = LoggingReporter(log)
warnings = checkRecursive([tempdir], reporter)
self.assertEqual(warnings, 2)
self.assertEqual(
sorted(log),
sorted([('flake', str(UnusedImport(file1, Node(1), 'baz'))),
('flake',
str(UnusedImport(file2, Node(1), 'contraband')))]))
class IntegrationTests(TestCase):
"""
Tests of the pyflakes script that actually spawn the script.
"""
def setUp(self):
self.tempdir = tempfile.mkdtemp()
self.tempfilepath = os.path.join(self.tempdir, 'temp')
def tearDown(self):
shutil.rmtree(self.tempdir)
def getPyflakesBinary(self):
"""
Return the path to the pyflakes binary.
"""
import pyflakes
package_dir = os.path.dirname(pyflakes.__file__)
return os.path.join(package_dir, '..', 'bin', 'pyflakes')
def runPyflakes(self, paths, stdin=None):
"""
Launch a subprocess running C{pyflakes}.
@param args: Command-line arguments to pass to pyflakes.
@param kwargs: Options passed on to C{subprocess.Popen}.
@return: C{(returncode, stdout, stderr)} of the completed pyflakes
process.
"""
env = dict(os.environ)
env['PYTHONPATH'] = os.pathsep.join(sys.path)
command = [sys.executable, self.getPyflakesBinary()]
command.extend(paths)
if stdin:
p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = p.communicate(stdin)
else:
p = subprocess.Popen(command, env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = p.communicate()
rv = p.wait()
if sys.version_info >= (3,):
stdout = stdout.decode('utf-8')
stderr = stderr.decode('utf-8')
return (stdout, stderr, rv)
def test_goodFile(self):
"""
When a Python source file is all good, the return code is zero and no
messages are printed to either stdout or stderr.
"""
fd = open(self.tempfilepath, 'a')
fd.close()
d = self.runPyflakes([self.tempfilepath])
self.assertEqual(d, ('', '', 0))
def test_fileWithFlakes(self):
"""
When a Python source file has warnings, the return code is non-zero
and the warnings are printed to stdout.
"""
fd = open(self.tempfilepath, 'wb')
fd.write("import contraband\n".encode('ascii'))
fd.close()
d = self.runPyflakes([self.tempfilepath])
expected = UnusedImport(self.tempfilepath, Node(1), 'contraband')
self.assertEqual(d, ("%s\n" % expected, '', 1))
def test_errors(self):
"""
When pyflakes finds errors with the files it's given, (if they don't
exist, say), then the return code is non-zero and the errors are
printed to stderr.
"""
d = self.runPyflakes([self.tempfilepath])
error_msg = '%s: No such file or directory\n' % (self.tempfilepath,)
self.assertEqual(d, ('', error_msg, 1))
def test_readFromStdin(self):
"""
If no arguments are passed to C{pyflakes} then it reads from stdin.
"""
d = self.runPyflakes([], stdin='import contraband'.encode('ascii'))
expected = UnusedImport('<stdin>', Node(1), 'contraband')
self.assertEqual(d, ("%s\n" % expected, '', 1))

View File

@ -0,0 +1,256 @@
import textwrap
from pyflakes import messages as m
from pyflakes.test.test_other import Test as TestOther
from pyflakes.test.test_imports import Test as TestImports
from pyflakes.test.test_undefined_names import Test as TestUndefinedNames
from pyflakes.test.harness import TestCase, skip
class _DoctestMixin(object):
withDoctest = True
def doctestify(self, input):
lines = []
for line in textwrap.dedent(input).splitlines():
if line.strip() == '':
pass
elif (line.startswith(' ') or
line.startswith('except:') or
line.startswith('except ') or
line.startswith('finally:') or
line.startswith('else:') or
line.startswith('elif ')):
line = "... %s" % line
else:
line = ">>> %s" % line
lines.append(line)
doctestificator = textwrap.dedent('''\
def doctest_something():
"""
%s
"""
''')
return doctestificator % "\n ".join(lines)
def flakes(self, input, *args, **kw):
return super(_DoctestMixin, self).flakes(self.doctestify(input), *args, **kw)
class Test(TestCase):
withDoctest = True
def test_importBeforeDoctest(self):
self.flakes("""
import foo
def doctest_stuff():
'''
>>> foo
'''
""")
@skip("todo")
def test_importBeforeAndInDoctest(self):
self.flakes('''
import foo
def doctest_stuff():
"""
>>> import foo
>>> foo
"""
foo
''', m.Redefined)
def test_importInDoctestAndAfter(self):
self.flakes('''
def doctest_stuff():
"""
>>> import foo
>>> foo
"""
import foo
foo()
''')
def test_offsetInDoctests(self):
exc = self.flakes('''
def doctest_stuff():
"""
>>> x # line 5
"""
''', m.UndefinedName).messages[0]
self.assertEqual(exc.lineno, 5)
self.assertEqual(exc.col, 12)
def test_offsetInLambdasInDoctests(self):
exc = self.flakes('''
def doctest_stuff():
"""
>>> lambda: x # line 5
"""
''', m.UndefinedName).messages[0]
self.assertEqual(exc.lineno, 5)
self.assertEqual(exc.col, 20)
def test_offsetAfterDoctests(self):
exc = self.flakes('''
def doctest_stuff():
"""
>>> x = 5
"""
x
''', m.UndefinedName).messages[0]
self.assertEqual(exc.lineno, 8)
self.assertEqual(exc.col, 0)
def test_syntaxErrorInDoctest(self):
exceptions = self.flakes(
'''
def doctest_stuff():
"""
>>> from # line 4
>>> fortytwo = 42
>>> except Exception:
"""
''',
m.DoctestSyntaxError,
m.DoctestSyntaxError,
m.DoctestSyntaxError).messages
exc = exceptions[0]
self.assertEqual(exc.lineno, 4)
self.assertEqual(exc.col, 26)
exc = exceptions[1]
self.assertEqual(exc.lineno, 5)
self.assertEqual(exc.col, 16)
exc = exceptions[2]
self.assertEqual(exc.lineno, 6)
self.assertEqual(exc.col, 18)
def test_indentationErrorInDoctest(self):
exc = self.flakes('''
def doctest_stuff():
"""
>>> if True:
... pass
"""
''', m.DoctestSyntaxError).messages[0]
self.assertEqual(exc.lineno, 5)
self.assertEqual(exc.col, 16)
def test_offsetWithMultiLineArgs(self):
(exc1, exc2) = self.flakes(
'''
def doctest_stuff(arg1,
arg2,
arg3):
"""
>>> assert
>>> this
"""
''',
m.DoctestSyntaxError,
m.UndefinedName).messages
self.assertEqual(exc1.lineno, 6)
self.assertEqual(exc1.col, 19)
self.assertEqual(exc2.lineno, 7)
self.assertEqual(exc2.col, 12)
def test_doctestCanReferToFunction(self):
self.flakes("""
def foo():
'''
>>> foo
'''
""")
def test_doctestCanReferToClass(self):
self.flakes("""
class Foo():
'''
>>> Foo
'''
def bar(self):
'''
>>> Foo
'''
""")
def test_noOffsetSyntaxErrorInDoctest(self):
exceptions = self.flakes(
'''
def buildurl(base, *args, **kwargs):
"""
>>> buildurl('/blah.php', ('a', '&'), ('b', '=')
'/blah.php?a=%26&b=%3D'
>>> buildurl('/blah.php', a='&', 'b'='=')
'/blah.php?b=%3D&a=%26'
"""
pass
''',
m.DoctestSyntaxError,
m.DoctestSyntaxError).messages
exc = exceptions[0]
self.assertEqual(exc.lineno, 4)
exc = exceptions[1]
self.assertEqual(exc.lineno, 6)
def test_singleUnderscoreInDoctest(self):
self.flakes('''
def func():
"""A docstring
>>> func()
1
>>> _
1
"""
return 1
''')
class TestOther(_DoctestMixin, TestOther):
pass
class TestImports(_DoctestMixin, TestImports):
def test_futureImport(self):
"""XXX This test can't work in a doctest"""
def test_futureImportUsed(self):
"""XXX This test can't work in a doctest"""
class TestUndefinedNames(_DoctestMixin, TestUndefinedNames):
def test_doubleNestingReportsClosestName(self):
"""
Lines in doctest are a bit different so we can't use the test
from TestUndefinedNames
"""
exc = self.flakes('''
def a():
x = 1
def b():
x = 2 # line 7 in the file
def c():
x
x = 3
return x
return x
return x
''', m.UndefinedLocal).messages[0]
self.assertEqual(exc.message_args, ('x', 7))

View File

@ -0,0 +1,881 @@
from sys import version_info
from pyflakes import messages as m
from pyflakes.test.harness import TestCase, skip, skipIf
class Test(TestCase):
def test_unusedImport(self):
self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport)
self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport)
def test_aliasedImport(self):
self.flakes('import fu as FU, bar as FU',
m.RedefinedWhileUnused, m.UnusedImport)
self.flakes('from moo import fu as FU, bar as FU',
m.RedefinedWhileUnused, m.UnusedImport)
def test_usedImport(self):
self.flakes('import fu; print(fu)')
self.flakes('from baz import fu; print(fu)')
self.flakes('import fu; del fu')
def test_redefinedWhileUnused(self):
self.flakes('import fu; fu = 3', m.RedefinedWhileUnused)
self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused)
self.flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused)
def test_redefinedIf(self):
"""
Test that importing a module twice within an if
block does raise a warning.
"""
self.flakes('''
i = 2
if i==1:
import os
import os
os.path''', m.RedefinedWhileUnused)
def test_redefinedIfElse(self):
"""
Test that importing a module twice in if
and else blocks does not raise a warning.
"""
self.flakes('''
i = 2
if i==1:
import os
else:
import os
os.path''')
def test_redefinedTry(self):
"""
Test that importing a module twice in an try block
does raise a warning.
"""
self.flakes('''
try:
import os
import os
except:
pass
os.path''', m.RedefinedWhileUnused)
def test_redefinedTryExcept(self):
"""
Test that importing a module twice in an try
and except block does not raise a warning.
"""
self.flakes('''
try:
import os
except:
import os
os.path''')
def test_redefinedTryNested(self):
"""
Test that importing a module twice using a nested
try/except and if blocks does not issue a warning.
"""
self.flakes('''
try:
if True:
if True:
import os
except:
import os
os.path''')
def test_redefinedTryExceptMulti(self):
self.flakes("""
try:
from aa import mixer
except AttributeError:
from bb import mixer
except RuntimeError:
from cc import mixer
except:
from dd import mixer
mixer(123)
""")
def test_redefinedTryElse(self):
self.flakes("""
try:
from aa import mixer
except ImportError:
pass
else:
from bb import mixer
mixer(123)
""", m.RedefinedWhileUnused)
def test_redefinedTryExceptElse(self):
self.flakes("""
try:
import funca
except ImportError:
from bb import funca
from bb import funcb
else:
from bbb import funcb
print(funca, funcb)
""")
def test_redefinedTryExceptFinally(self):
self.flakes("""
try:
from aa import a
except ImportError:
from bb import a
finally:
a = 42
print(a)
""")
def test_redefinedTryExceptElseFinally(self):
self.flakes("""
try:
import b
except ImportError:
b = Ellipsis
from bb import a
else:
from aa import a
finally:
a = 42
print(a, b)
""")
def test_redefinedByFunction(self):
self.flakes('''
import fu
def fu():
pass
''', m.RedefinedWhileUnused)
def test_redefinedInNestedFunction(self):
"""
Test that shadowing a global name with a nested function definition
generates a warning.
"""
self.flakes('''
import fu
def bar():
def baz():
def fu():
pass
''', m.RedefinedWhileUnused, m.UnusedImport)
def test_redefinedInNestedFunctionTwice(self):
"""
Test that shadowing a global name with a nested function definition
generates a warning.
"""
self.flakes('''
import fu
def bar():
import fu
def baz():
def fu():
pass
''', m.RedefinedWhileUnused, m.RedefinedWhileUnused,
m.UnusedImport, m.UnusedImport)
def test_redefinedButUsedLater(self):
"""
Test that a global import which is redefined locally,
but used later in another scope does not generate a warning.
"""
self.flakes('''
import unittest, transport
class GetTransportTestCase(unittest.TestCase):
def test_get_transport(self):
transport = 'transport'
self.assertIsNotNone(transport)
class TestTransportMethodArgs(unittest.TestCase):
def test_send_defaults(self):
transport.Transport()
''')
def test_redefinedByClass(self):
self.flakes('''
import fu
class fu:
pass
''', m.RedefinedWhileUnused)
def test_redefinedBySubclass(self):
"""
If an imported name is redefined by a class statement which also uses
that name in the bases list, no warning is emitted.
"""
self.flakes('''
from fu import bar
class bar(bar):
pass
''')
def test_redefinedInClass(self):
"""
Test that shadowing a global with a class attribute does not produce a
warning.
"""
self.flakes('''
import fu
class bar:
fu = 1
print(fu)
''')
def test_usedInFunction(self):
self.flakes('''
import fu
def fun():
print(fu)
''')
def test_shadowedByParameter(self):
self.flakes('''
import fu
def fun(fu):
print(fu)
''', m.UnusedImport, m.RedefinedWhileUnused)
self.flakes('''
import fu
def fun(fu):
print(fu)
print(fu)
''')
def test_newAssignment(self):
self.flakes('fu = None')
def test_usedInGetattr(self):
self.flakes('import fu; fu.bar.baz')
self.flakes('import fu; "bar".fu.baz', m.UnusedImport)
def test_usedInSlice(self):
self.flakes('import fu; print(fu.bar[1:])')
def test_usedInIfBody(self):
self.flakes('''
import fu
if True: print(fu)
''')
def test_usedInIfConditional(self):
self.flakes('''
import fu
if fu: pass
''')
def test_usedInElifConditional(self):
self.flakes('''
import fu
if False: pass
elif fu: pass
''')
def test_usedInElse(self):
self.flakes('''
import fu
if False: pass
else: print(fu)
''')
def test_usedInCall(self):
self.flakes('import fu; fu.bar()')
def test_usedInClass(self):
self.flakes('''
import fu
class bar:
bar = fu
''')
def test_usedInClassBase(self):
self.flakes('''
import fu
class bar(object, fu.baz):
pass
''')
def test_notUsedInNestedScope(self):
self.flakes('''
import fu
def bleh():
pass
print(fu)
''')
def test_usedInFor(self):
self.flakes('''
import fu
for bar in range(9):
print(fu)
''')
def test_usedInForElse(self):
self.flakes('''
import fu
for bar in range(10):
pass
else:
print(fu)
''')
def test_redefinedByFor(self):
self.flakes('''
import fu
for fu in range(2):
pass
''', m.ImportShadowedByLoopVar)
def test_shadowedByFor(self):
"""
Test that shadowing a global name with a for loop variable generates a
warning.
"""
self.flakes('''
import fu
fu.bar()
for fu in ():
pass
''', m.ImportShadowedByLoopVar)
def test_shadowedByForDeep(self):
"""
Test that shadowing a global name with a for loop variable nested in a
tuple unpack generates a warning.
"""
self.flakes('''
import fu
fu.bar()
for (x, y, z, (a, b, c, (fu,))) in ():
pass
''', m.ImportShadowedByLoopVar)
# Same with a list instead of a tuple
self.flakes('''
import fu
fu.bar()
for [x, y, z, (a, b, c, (fu,))] in ():
pass
''', m.ImportShadowedByLoopVar)
def test_usedInReturn(self):
self.flakes('''
import fu
def fun():
return fu
''')
def test_usedInOperators(self):
self.flakes('import fu; 3 + fu.bar')
self.flakes('import fu; 3 % fu.bar')
self.flakes('import fu; 3 - fu.bar')
self.flakes('import fu; 3 * fu.bar')
self.flakes('import fu; 3 ** fu.bar')
self.flakes('import fu; 3 / fu.bar')
self.flakes('import fu; 3 // fu.bar')
self.flakes('import fu; -fu.bar')
self.flakes('import fu; ~fu.bar')
self.flakes('import fu; 1 == fu.bar')
self.flakes('import fu; 1 | fu.bar')
self.flakes('import fu; 1 & fu.bar')
self.flakes('import fu; 1 ^ fu.bar')
self.flakes('import fu; 1 >> fu.bar')
self.flakes('import fu; 1 << fu.bar')
def test_usedInAssert(self):
self.flakes('import fu; assert fu.bar')
def test_usedInSubscript(self):
self.flakes('import fu; fu.bar[1]')
def test_usedInLogic(self):
self.flakes('import fu; fu and False')
self.flakes('import fu; fu or False')
self.flakes('import fu; not fu.bar')
def test_usedInList(self):
self.flakes('import fu; [fu]')
def test_usedInTuple(self):
self.flakes('import fu; (fu,)')
def test_usedInTry(self):
self.flakes('''
import fu
try: fu
except: pass
''')
def test_usedInExcept(self):
self.flakes('''
import fu
try: fu
except: pass
''')
def test_redefinedByExcept(self):
as_exc = ', ' if version_info < (2, 6) else ' as '
self.flakes('''
import fu
try: pass
except Exception%sfu: pass
''' % as_exc, m.RedefinedWhileUnused)
def test_usedInRaise(self):
self.flakes('''
import fu
raise fu.bar
''')
def test_usedInYield(self):
self.flakes('''
import fu
def gen():
yield fu
''')
def test_usedInDict(self):
self.flakes('import fu; {fu:None}')
self.flakes('import fu; {1:fu}')
def test_usedInParameterDefault(self):
self.flakes('''
import fu
def f(bar=fu):
pass
''')
def test_usedInAttributeAssign(self):
self.flakes('import fu; fu.bar = 1')
def test_usedInKeywordArg(self):
self.flakes('import fu; fu.bar(stuff=fu)')
def test_usedInAssignment(self):
self.flakes('import fu; bar=fu')
self.flakes('import fu; n=0; n+=fu')
def test_usedInListComp(self):
self.flakes('import fu; [fu for _ in range(1)]')
self.flakes('import fu; [1 for _ in range(1) if fu]')
def test_redefinedByListComp(self):
self.flakes('import fu; [1 for fu in range(1)]',
m.RedefinedInListComp)
def test_usedInTryFinally(self):
self.flakes('''
import fu
try: pass
finally: fu
''')
self.flakes('''
import fu
try: fu
finally: pass
''')
def test_usedInWhile(self):
self.flakes('''
import fu
while 0:
fu
''')
self.flakes('''
import fu
while fu: pass
''')
def test_usedInGlobal(self):
self.flakes('''
import fu
def f(): global fu
''', m.UnusedImport)
@skipIf(version_info >= (3,), 'deprecated syntax')
def test_usedInBackquote(self):
self.flakes('import fu; `fu`')
def test_usedInExec(self):
if version_info < (3,):
exec_stmt = 'exec "print 1" in fu.bar'
else:
exec_stmt = 'exec("print(1)", fu.bar)'
self.flakes('import fu; %s' % exec_stmt)
def test_usedInLambda(self):
self.flakes('import fu; lambda: fu')
def test_shadowedByLambda(self):
self.flakes('import fu; lambda fu: fu',
m.UnusedImport, m.RedefinedWhileUnused)
self.flakes('import fu; lambda fu: fu\nfu()')
def test_usedInSliceObj(self):
self.flakes('import fu; "meow"[::fu]')
def test_unusedInNestedScope(self):
self.flakes('''
def bar():
import fu
fu
''', m.UnusedImport, m.UndefinedName)
def test_methodsDontUseClassScope(self):
self.flakes('''
class bar:
import fu
def fun(self):
fu
''', m.UnusedImport, m.UndefinedName)
def test_nestedFunctionsNestScope(self):
self.flakes('''
def a():
def b():
fu
import fu
''')
def test_nestedClassAndFunctionScope(self):
self.flakes('''
def a():
import fu
class b:
def c(self):
print(fu)
''')
def test_importStar(self):
self.flakes('from fu import *', m.ImportStarUsed)
def test_packageImport(self):
"""
If a dotted name is imported and used, no warning is reported.
"""
self.flakes('''
import fu.bar
fu.bar
''')
def test_unusedPackageImport(self):
"""
If a dotted name is imported and not used, an unused import warning is
reported.
"""
self.flakes('import fu.bar', m.UnusedImport)
def test_duplicateSubmoduleImport(self):
"""
If a submodule of a package is imported twice, an unused import warning
and a redefined while unused warning are reported.
"""
self.flakes('''
import fu.bar, fu.bar
fu.bar
''', m.RedefinedWhileUnused)
self.flakes('''
import fu.bar
import fu.bar
fu.bar
''', m.RedefinedWhileUnused)
def test_differentSubmoduleImport(self):
"""
If two different submodules of a package are imported, no duplicate
import warning is reported for the package.
"""
self.flakes('''
import fu.bar, fu.baz
fu.bar, fu.baz
''')
self.flakes('''
import fu.bar
import fu.baz
fu.bar, fu.baz
''')
def test_assignRHSFirst(self):
self.flakes('import fu; fu = fu')
self.flakes('import fu; fu, bar = fu')
self.flakes('import fu; [fu, bar] = fu')
self.flakes('import fu; fu += fu')
def test_tryingMultipleImports(self):
self.flakes('''
try:
import fu
except ImportError:
import bar as fu
fu
''')
def test_nonGlobalDoesNotRedefine(self):
self.flakes('''
import fu
def a():
fu = 3
return fu
fu
''')
def test_functionsRunLater(self):
self.flakes('''
def a():
fu
import fu
''')
def test_functionNamesAreBoundNow(self):
self.flakes('''
import fu
def fu():
fu
fu
''', m.RedefinedWhileUnused)
def test_ignoreNonImportRedefinitions(self):
self.flakes('a = 1; a = 2')
@skip("todo")
def test_importingForImportError(self):
self.flakes('''
try:
import fu
except ImportError:
pass
''')
@skip("todo: requires evaluating attribute access")
def test_importedInClass(self):
"""Imports in class scope can be used through self."""
self.flakes('''
class c:
import i
def __init__(self):
self.i
''')
def test_importUsedInMethodDefinition(self):
"""
Method named 'foo' with default args referring to module named 'foo'.
"""
self.flakes('''
import foo
class Thing(object):
def foo(self, parser=foo.parse_foo):
pass
''')
def test_futureImport(self):
"""__future__ is special."""
self.flakes('from __future__ import division')
self.flakes('''
"docstring is allowed before future import"
from __future__ import division
''')
def test_futureImportFirst(self):
"""
__future__ imports must come before anything else.
"""
self.flakes('''
x = 5
from __future__ import division
''', m.LateFutureImport)
self.flakes('''
from foo import bar
from __future__ import division
bar
''', m.LateFutureImport)
def test_futureImportUsed(self):
"""__future__ is special, but names are injected in the namespace."""
self.flakes('''
from __future__ import division
from __future__ import print_function
assert print_function is not division
''')
class TestSpecialAll(TestCase):
"""
Tests for suppression of unused import warnings by C{__all__}.
"""
def test_ignoredInFunction(self):
"""
An C{__all__} definition does not suppress unused import warnings in a
function scope.
"""
self.flakes('''
def foo():
import bar
__all__ = ["bar"]
''', m.UnusedImport, m.UnusedVariable)
def test_ignoredInClass(self):
"""
An C{__all__} definition does not suppress unused import warnings in a
class scope.
"""
self.flakes('''
class foo:
import bar
__all__ = ["bar"]
''', m.UnusedImport)
def test_warningSuppressed(self):
"""
If a name is imported and unused but is named in C{__all__}, no warning
is reported.
"""
self.flakes('''
import foo
__all__ = ["foo"]
''')
self.flakes('''
import foo
__all__ = ("foo",)
''')
def test_augmentedAssignment(self):
"""
The C{__all__} variable is defined incrementally.
"""
self.flakes('''
import a
import c
__all__ = ['a']
__all__ += ['b']
if 1 < 3:
__all__ += ['c', 'd']
''', m.UndefinedExport, m.UndefinedExport)
def test_unrecognizable(self):
"""
If C{__all__} is defined in a way that can't be recognized statically,
it is ignored.
"""
self.flakes('''
import foo
__all__ = ["f" + "oo"]
''', m.UnusedImport)
self.flakes('''
import foo
__all__ = [] + ["foo"]
''', m.UnusedImport)
def test_unboundExported(self):
"""
If C{__all__} includes a name which is not bound, a warning is emitted.
"""
self.flakes('''
__all__ = ["foo"]
''', m.UndefinedExport)
# Skip this in __init__.py though, since the rules there are a little
# different.
for filename in ["foo/__init__.py", "__init__.py"]:
self.flakes('''
__all__ = ["foo"]
''', filename=filename)
def test_importStarExported(self):
"""
Do not report undefined if import * is used
"""
self.flakes('''
from foolib import *
__all__ = ["foo"]
''', m.ImportStarUsed)
def test_usedInGenExp(self):
"""
Using a global in a generator expression results in no warnings.
"""
self.flakes('import fu; (fu for _ in range(1))')
self.flakes('import fu; (1 for _ in range(1) if fu)')
def test_redefinedByGenExp(self):
"""
Re-using a global name as the loop variable for a generator
expression results in a redefinition warning.
"""
self.flakes('import fu; (1 for fu in range(1))',
m.RedefinedWhileUnused, m.UnusedImport)
def test_usedAsDecorator(self):
"""
Using a global name in a decorator statement results in no warnings,
but using an undefined name in a decorator statement results in an
undefined name warning.
"""
self.flakes('''
from interior import decorate
@decorate
def f():
return "hello"
''')
self.flakes('''
from interior import decorate
@decorate('value')
def f():
return "hello"
''')
self.flakes('''
@decorate
def f():
return "hello"
''', m.UndefinedName)
class Python26Tests(TestCase):
"""
Tests for checking of syntax which is valid in PYthon 2.6 and newer.
"""
@skipIf(version_info < (2, 6), "Python >= 2.6 only")
def test_usedAsClassDecorator(self):
"""
Using an imported name as a class decorator results in no warnings,
but using an undefined name as a class decorator results in an
undefined name warning.
"""
self.flakes('''
from interior import decorate
@decorate
class foo:
pass
''')
self.flakes('''
from interior import decorate
@decorate("foo")
class bar:
pass
''')
self.flakes('''
@decorate
class foo:
pass
''', m.UndefinedName)

View File

@ -0,0 +1,940 @@
"""
Tests for various Pyflakes behavior.
"""
from sys import version_info
from pyflakes import messages as m
from pyflakes.test.harness import TestCase, skip, skipIf
class Test(TestCase):
def test_duplicateArgs(self):
self.flakes('def fu(bar, bar): pass', m.DuplicateArgument)
def test_localReferencedBeforeAssignment(self):
self.flakes('''
a = 1
def f():
a; a=1
f()
''', m.UndefinedLocal, m.UnusedVariable)
def test_redefinedInListComp(self):
"""
Test that shadowing a variable in a list comprehension raises
a warning.
"""
self.flakes('''
a = 1
[1 for a, b in [(1, 2)]]
''', m.RedefinedInListComp)
self.flakes('''
class A:
a = 1
[1 for a, b in [(1, 2)]]
''', m.RedefinedInListComp)
self.flakes('''
def f():
a = 1
[1 for a, b in [(1, 2)]]
''', m.RedefinedInListComp)
self.flakes('''
[1 for a, b in [(1, 2)]]
[1 for a, b in [(1, 2)]]
''')
self.flakes('''
for a, b in [(1, 2)]:
pass
[1 for a, b in [(1, 2)]]
''')
def test_redefinedInGenerator(self):
"""
Test that reusing a variable in a generator does not raise
a warning.
"""
self.flakes('''
a = 1
(1 for a, b in [(1, 2)])
''')
self.flakes('''
class A:
a = 1
list(1 for a, b in [(1, 2)])
''')
self.flakes('''
def f():
a = 1
(1 for a, b in [(1, 2)])
''', m.UnusedVariable)
self.flakes('''
(1 for a, b in [(1, 2)])
(1 for a, b in [(1, 2)])
''')
self.flakes('''
for a, b in [(1, 2)]:
pass
(1 for a, b in [(1, 2)])
''')
@skipIf(version_info < (2, 7), "Python >= 2.7 only")
def test_redefinedInSetComprehension(self):
"""
Test that reusing a variable in a set comprehension does not raise
a warning.
"""
self.flakes('''
a = 1
{1 for a, b in [(1, 2)]}
''')
self.flakes('''
class A:
a = 1
{1 for a, b in [(1, 2)]}
''')
self.flakes('''
def f():
a = 1
{1 for a, b in [(1, 2)]}
''', m.UnusedVariable)
self.flakes('''
{1 for a, b in [(1, 2)]}
{1 for a, b in [(1, 2)]}
''')
self.flakes('''
for a, b in [(1, 2)]:
pass
{1 for a, b in [(1, 2)]}
''')
@skipIf(version_info < (2, 7), "Python >= 2.7 only")
def test_redefinedInDictComprehension(self):
"""
Test that reusing a variable in a dict comprehension does not raise
a warning.
"""
self.flakes('''
a = 1
{1: 42 for a, b in [(1, 2)]}
''')
self.flakes('''
class A:
a = 1
{1: 42 for a, b in [(1, 2)]}
''')
self.flakes('''
def f():
a = 1
{1: 42 for a, b in [(1, 2)]}
''', m.UnusedVariable)
self.flakes('''
{1: 42 for a, b in [(1, 2)]}
{1: 42 for a, b in [(1, 2)]}
''')
self.flakes('''
for a, b in [(1, 2)]:
pass
{1: 42 for a, b in [(1, 2)]}
''')
def test_redefinedFunction(self):
"""
Test that shadowing a function definition with another one raises a
warning.
"""
self.flakes('''
def a(): pass
def a(): pass
''', m.RedefinedWhileUnused)
def test_redefinedClassFunction(self):
"""
Test that shadowing a function definition in a class suite with another
one raises a warning.
"""
self.flakes('''
class A:
def a(): pass
def a(): pass
''', m.RedefinedWhileUnused)
def test_redefinedIfElseFunction(self):
"""
Test that shadowing a function definition twice in an if
and else block does not raise a warning.
"""
self.flakes('''
if True:
def a(): pass
else:
def a(): pass
''')
def test_redefinedIfFunction(self):
"""
Test that shadowing a function definition within an if block
raises a warning.
"""
self.flakes('''
if True:
def a(): pass
def a(): pass
''', m.RedefinedWhileUnused)
def test_redefinedTryExceptFunction(self):
"""
Test that shadowing a function definition twice in try
and except block does not raise a warning.
"""
self.flakes('''
try:
def a(): pass
except:
def a(): pass
''')
def test_redefinedTryFunction(self):
"""
Test that shadowing a function definition within a try block
raises a warning.
"""
self.flakes('''
try:
def a(): pass
def a(): pass
except:
pass
''', m.RedefinedWhileUnused)
def test_redefinedIfElseInListComp(self):
"""
Test that shadowing a variable in a list comprehension in
an if and else block does not raise a warning.
"""
self.flakes('''
if False:
a = 1
else:
[a for a in '12']
''')
def test_redefinedElseInListComp(self):
"""
Test that shadowing a variable in a list comprehension in
an else (or if) block raises a warning.
"""
self.flakes('''
if False:
pass
else:
a = 1
[a for a in '12']
''', m.RedefinedInListComp)
def test_functionDecorator(self):
"""
Test that shadowing a function definition with a decorated version of
that function does not raise a warning.
"""
self.flakes('''
from somewhere import somedecorator
def a(): pass
a = somedecorator(a)
''')
def test_classFunctionDecorator(self):
"""
Test that shadowing a function definition in a class suite with a
decorated version of that function does not raise a warning.
"""
self.flakes('''
class A:
def a(): pass
a = classmethod(a)
''')
@skipIf(version_info < (2, 6), "Python >= 2.6 only")
def test_modernProperty(self):
self.flakes("""
class A:
@property
def t(self):
pass
@t.setter
def t(self, value):
pass
@t.deleter
def t(self):
pass
""")
def test_unaryPlus(self):
"""Don't die on unary +."""
self.flakes('+1')
def test_undefinedBaseClass(self):
"""
If a name in the base list of a class definition is undefined, a
warning is emitted.
"""
self.flakes('''
class foo(foo):
pass
''', m.UndefinedName)
def test_classNameUndefinedInClassBody(self):
"""
If a class name is used in the body of that class's definition and
the name is not already defined, a warning is emitted.
"""
self.flakes('''
class foo:
foo
''', m.UndefinedName)
def test_classNameDefinedPreviously(self):
"""
If a class name is used in the body of that class's definition and
the name was previously defined in some other way, no warning is
emitted.
"""
self.flakes('''
foo = None
class foo:
foo
''')
def test_classRedefinition(self):
"""
If a class is defined twice in the same module, a warning is emitted.
"""
self.flakes('''
class Foo:
pass
class Foo:
pass
''', m.RedefinedWhileUnused)
def test_functionRedefinedAsClass(self):
"""
If a function is redefined as a class, a warning is emitted.
"""
self.flakes('''
def Foo():
pass
class Foo:
pass
''', m.RedefinedWhileUnused)
def test_classRedefinedAsFunction(self):
"""
If a class is redefined as a function, a warning is emitted.
"""
self.flakes('''
class Foo:
pass
def Foo():
pass
''', m.RedefinedWhileUnused)
@skip("todo: Too hard to make this warn but other cases stay silent")
def test_doubleAssignment(self):
"""
If a variable is re-assigned to without being used, no warning is
emitted.
"""
self.flakes('''
x = 10
x = 20
''', m.RedefinedWhileUnused)
def test_doubleAssignmentConditionally(self):
"""
If a variable is re-assigned within a conditional, no warning is
emitted.
"""
self.flakes('''
x = 10
if True:
x = 20
''')
def test_doubleAssignmentWithUse(self):
"""
If a variable is re-assigned to after being used, no warning is
emitted.
"""
self.flakes('''
x = 10
y = x * 2
x = 20
''')
def test_comparison(self):
"""
If a defined name is used on either side of any of the six comparison
operators, no warning is emitted.
"""
self.flakes('''
x = 10
y = 20
x < y
x <= y
x == y
x != y
x >= y
x > y
''')
def test_identity(self):
"""
If a defined name is used on either side of an identity test, no
warning is emitted.
"""
self.flakes('''
x = 10
y = 20
x is y
x is not y
''')
def test_containment(self):
"""
If a defined name is used on either side of a containment test, no
warning is emitted.
"""
self.flakes('''
x = 10
y = 20
x in y
x not in y
''')
def test_loopControl(self):
"""
break and continue statements are supported.
"""
self.flakes('''
for x in [1, 2]:
break
''')
self.flakes('''
for x in [1, 2]:
continue
''')
def test_ellipsis(self):
"""
Ellipsis in a slice is supported.
"""
self.flakes('''
[1, 2][...]
''')
def test_extendedSlice(self):
"""
Extended slices are supported.
"""
self.flakes('''
x = 3
[1, 2][x,:]
''')
def test_varAugmentedAssignment(self):
"""
Augmented assignment of a variable is supported.
We don't care about var refs.
"""
self.flakes('''
foo = 0
foo += 1
''')
def test_attrAugmentedAssignment(self):
"""
Augmented assignment of attributes is supported.
We don't care about attr refs.
"""
self.flakes('''
foo = None
foo.bar += foo.baz
''')
class TestUnusedAssignment(TestCase):
"""
Tests for warning about unused assignments.
"""
def test_unusedVariable(self):
"""
Warn when a variable in a function is assigned a value that's never
used.
"""
self.flakes('''
def a():
b = 1
''', m.UnusedVariable)
def test_unusedVariableAsLocals(self):
"""
Using locals() it is perfectly valid to have unused variables
"""
self.flakes('''
def a():
b = 1
return locals()
''')
def test_unusedVariableNoLocals(self):
"""
Using locals() in wrong scope should not matter
"""
self.flakes('''
def a():
locals()
def a():
b = 1
return
''', m.UnusedVariable)
def test_assignToGlobal(self):
"""
Assigning to a global and then not using that global is perfectly
acceptable. Do not mistake it for an unused local variable.
"""
self.flakes('''
b = 0
def a():
global b
b = 1
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_assignToNonlocal(self):
"""
Assigning to a nonlocal and then not using that binding is perfectly
acceptable. Do not mistake it for an unused local variable.
"""
self.flakes('''
b = b'0'
def a():
nonlocal b
b = b'1'
''')
def test_assignToMember(self):
"""
Assigning to a member of another object and then not using that member
variable is perfectly acceptable. Do not mistake it for an unused
local variable.
"""
# XXX: Adding this test didn't generate a failure. Maybe not
# necessary?
self.flakes('''
class b:
pass
def a():
b.foo = 1
''')
def test_assignInForLoop(self):
"""
Don't warn when a variable in a for loop is assigned to but not used.
"""
self.flakes('''
def f():
for i in range(10):
pass
''')
def test_assignInListComprehension(self):
"""
Don't warn when a variable in a list comprehension is
assigned to but not used.
"""
self.flakes('''
def f():
[None for i in range(10)]
''')
def test_generatorExpression(self):
"""
Don't warn when a variable in a generator expression is
assigned to but not used.
"""
self.flakes('''
def f():
(None for i in range(10))
''')
def test_assignmentInsideLoop(self):
"""
Don't warn when a variable assignment occurs lexically after its use.
"""
self.flakes('''
def f():
x = None
for i in range(10):
if i > 2:
return x
x = i * 2
''')
def test_tupleUnpacking(self):
"""
Don't warn when a variable included in tuple unpacking is unused. It's
very common for variables in a tuple unpacking assignment to be unused
in good Python code, so warning will only create false positives.
"""
self.flakes('''
def f(tup):
(x, y) = tup
''')
self.flakes('''
def f():
(x, y) = 1, 2
''', m.UnusedVariable, m.UnusedVariable)
self.flakes('''
def f():
(x, y) = coords = 1, 2
if x > 1:
print(coords)
''')
self.flakes('''
def f():
(x, y) = coords = 1, 2
''', m.UnusedVariable)
self.flakes('''
def f():
coords = (x, y) = 1, 2
''', m.UnusedVariable)
def test_listUnpacking(self):
"""
Don't warn when a variable included in list unpacking is unused.
"""
self.flakes('''
def f(tup):
[x, y] = tup
''')
self.flakes('''
def f():
[x, y] = [1, 2]
''', m.UnusedVariable, m.UnusedVariable)
def test_closedOver(self):
"""
Don't warn when the assignment is used in an inner function.
"""
self.flakes('''
def barMaker():
foo = 5
def bar():
return foo
return bar
''')
def test_doubleClosedOver(self):
"""
Don't warn when the assignment is used in an inner function, even if
that inner function itself is in an inner function.
"""
self.flakes('''
def barMaker():
foo = 5
def bar():
def baz():
return foo
return bar
''')
def test_tracebackhideSpecialVariable(self):
"""
Do not warn about unused local variable __tracebackhide__, which is
a special variable for py.test.
"""
self.flakes("""
def helper():
__tracebackhide__ = True
""")
def test_ifexp(self):
"""
Test C{foo if bar else baz} statements.
"""
self.flakes("a = 'moo' if True else 'oink'")
self.flakes("a = foo if True else 'oink'", m.UndefinedName)
self.flakes("a = 'moo' if True else bar", m.UndefinedName)
def test_withStatementNoNames(self):
"""
No warnings are emitted for using inside or after a nameless C{with}
statement a name defined beforehand.
"""
self.flakes('''
from __future__ import with_statement
bar = None
with open("foo"):
bar
bar
''')
def test_withStatementSingleName(self):
"""
No warnings are emitted for using a name defined by a C{with} statement
within the suite or afterwards.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as bar:
bar
bar
''')
def test_withStatementAttributeName(self):
"""
No warnings are emitted for using an attribute as the target of a
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo.bar:
pass
''')
def test_withStatementSubscript(self):
"""
No warnings are emitted for using a subscript as the target of a
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo[0]:
pass
''')
def test_withStatementSubscriptUndefined(self):
"""
An undefined name warning is emitted if the subscript used as the
target of a C{with} statement is not defined.
"""
self.flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo[bar]:
pass
''', m.UndefinedName)
def test_withStatementTupleNames(self):
"""
No warnings are emitted for using any of the tuple of names defined by
a C{with} statement within the suite or afterwards.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as (bar, baz):
bar, baz
bar, baz
''')
def test_withStatementListNames(self):
"""
No warnings are emitted for using any of the list of names defined by a
C{with} statement within the suite or afterwards.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as [bar, baz]:
bar, baz
bar, baz
''')
def test_withStatementComplicatedTarget(self):
"""
If the target of a C{with} statement uses any or all of the valid forms
for that part of the grammar (See
U{http://docs.python.org/reference/compound_stmts.html#the-with-statement}),
the names involved are checked both for definedness and any bindings
created are respected in the suite of the statement and afterwards.
"""
self.flakes('''
from __future__ import with_statement
c = d = e = g = h = i = None
with open('foo') as [(a, b), c[d], e.f, g[h:i]]:
a, b, c, d, e, g, h, i
a, b, c, d, e, g, h, i
''')
def test_withStatementSingleNameUndefined(self):
"""
An undefined name warning is emitted if the name first defined by a
C{with} statement is used before the C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
bar
with open('foo') as bar:
pass
''', m.UndefinedName)
def test_withStatementTupleNamesUndefined(self):
"""
An undefined name warning is emitted if a name first defined by a the
tuple-unpacking form of the C{with} statement is used before the
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
baz
with open('foo') as (bar, baz):
pass
''', m.UndefinedName)
def test_withStatementSingleNameRedefined(self):
"""
A redefined name warning is emitted if a name bound by an import is
rebound by the name defined by a C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import bar
with open('foo') as bar:
pass
''', m.RedefinedWhileUnused)
def test_withStatementTupleNamesRedefined(self):
"""
A redefined name warning is emitted if a name bound by an import is
rebound by one of the names defined by the tuple-unpacking form of a
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import bar
with open('foo') as (bar, baz):
pass
''', m.RedefinedWhileUnused)
def test_withStatementUndefinedInside(self):
"""
An undefined name warning is emitted if a name is used inside the
body of a C{with} statement without first being bound.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as bar:
baz
''', m.UndefinedName)
def test_withStatementNameDefinedInBody(self):
"""
A name defined in the body of a C{with} statement can be used after
the body ends without warning.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as bar:
baz = 10
baz
''')
def test_withStatementUndefinedInExpression(self):
"""
An undefined name warning is emitted if a name in the I{test}
expression of a C{with} statement is undefined.
"""
self.flakes('''
from __future__ import with_statement
with bar as baz:
pass
''', m.UndefinedName)
self.flakes('''
from __future__ import with_statement
with bar as bar:
pass
''', m.UndefinedName)
@skipIf(version_info < (2, 7), "Python >= 2.7 only")
def test_dictComprehension(self):
"""
Dict comprehensions are properly handled.
"""
self.flakes('''
a = {1: x for x in range(10)}
''')
@skipIf(version_info < (2, 7), "Python >= 2.7 only")
def test_setComprehensionAndLiteral(self):
"""
Set comprehensions are properly handled.
"""
self.flakes('''
a = {1, 2, 3}
b = {x for x in range(10)}
''')
def test_exceptionUsedInExcept(self):
as_exc = ', ' if version_info < (2, 6) else ' as '
self.flakes('''
try: pass
except Exception%se: e
''' % as_exc)
self.flakes('''
def download_review():
try: pass
except Exception%se: e
''' % as_exc)
def test_exceptWithoutNameInFunction(self):
"""
Don't issue false warning when an unnamed exception is used.
Previously, there would be a false warning, but only when the
try..except was in a function
"""
self.flakes('''
import tokenize
def foo():
try: pass
except tokenize.TokenError: pass
''')
def test_exceptWithoutNameInFunctionTuple(self):
"""
Don't issue false warning when an unnamed exception is used.
This example catches a tuple of exception types.
"""
self.flakes('''
import tokenize
def foo():
try: pass
except (tokenize.TokenError, IndentationError): pass
''')
def test_augmentedAssignmentImportedFunctionCall(self):
"""
Consider a function that is called on the right part of an
augassign operation to be used.
"""
self.flakes('''
from foo import bar
baz = 0
baz += bar()
''')
@skipIf(version_info < (3, 3), 'new in Python 3.3')
def test_yieldFromUndefined(self):
"""
Test C{yield from} statement
"""
self.flakes('''
def bar():
yield from foo()
''', m.UndefinedName)

View File

@ -0,0 +1,34 @@
from sys import version_info
from pyflakes import messages as m
from pyflakes.test.harness import TestCase, skipIf
class Test(TestCase):
@skipIf(version_info >= (3, 3), 'new in Python 3.3')
def test_return(self):
self.flakes('''
class a:
def b():
for x in a.c:
if x:
yield x
return a
''', m.ReturnWithArgsInsideGenerator)
@skipIf(version_info >= (3, 3), 'new in Python 3.3')
def test_returnNone(self):
self.flakes('''
def a():
yield 12
return None
''', m.ReturnWithArgsInsideGenerator)
@skipIf(version_info >= (3, 3), 'new in Python 3.3')
def test_returnYieldExpression(self):
self.flakes('''
def a():
b = yield a
return b
''', m.ReturnWithArgsInsideGenerator)

View File

@ -0,0 +1,451 @@
from _ast import PyCF_ONLY_AST
from sys import version_info
from pyflakes import messages as m, checker
from pyflakes.test.harness import TestCase, skip, skipIf
class Test(TestCase):
def test_undefined(self):
self.flakes('bar', m.UndefinedName)
def test_definedInListComp(self):
self.flakes('[a for a in range(10) if a]')
def test_functionsNeedGlobalScope(self):
self.flakes('''
class a:
def b():
fu
fu = 1
''')
def test_builtins(self):
self.flakes('range(10)')
def test_builtinWindowsError(self):
"""
C{WindowsError} is sometimes a builtin name, so no warning is emitted
for using it.
"""
self.flakes('WindowsError')
def test_magicGlobalsFile(self):
"""
Use of the C{__file__} magic global should not emit an undefined name
warning.
"""
self.flakes('__file__')
def test_magicGlobalsBuiltins(self):
"""
Use of the C{__builtins__} magic global should not emit an undefined
name warning.
"""
self.flakes('__builtins__')
def test_magicGlobalsName(self):
"""
Use of the C{__name__} magic global should not emit an undefined name
warning.
"""
self.flakes('__name__')
def test_magicGlobalsPath(self):
"""
Use of the C{__path__} magic global should not emit an undefined name
warning, if you refer to it from a file called __init__.py.
"""
self.flakes('__path__', m.UndefinedName)
self.flakes('__path__', filename='package/__init__.py')
def test_globalImportStar(self):
"""Can't find undefined names with import *."""
self.flakes('from fu import *; bar', m.ImportStarUsed)
def test_localImportStar(self):
"""
A local import * still allows undefined names to be found
in upper scopes.
"""
self.flakes('''
def a():
from fu import *
bar
''', m.ImportStarUsed, m.UndefinedName)
@skipIf(version_info >= (3,), 'obsolete syntax')
def test_unpackedParameter(self):
"""Unpacked function parameters create bindings."""
self.flakes('''
def a((bar, baz)):
bar; baz
''')
@skip("todo")
def test_definedByGlobal(self):
"""
"global" can make an otherwise undefined name in another function
defined.
"""
self.flakes('''
def a(): global fu; fu = 1
def b(): fu
''')
def test_globalInGlobalScope(self):
"""
A global statement in the global scope is ignored.
"""
self.flakes('''
global x
def foo():
print(x)
''', m.UndefinedName)
def test_del(self):
"""Del deletes bindings."""
self.flakes('a = 1; del a; a', m.UndefinedName)
def test_delGlobal(self):
"""Del a global binding from a function."""
self.flakes('''
a = 1
def f():
global a
del a
a
''')
def test_delUndefined(self):
"""Del an undefined name."""
self.flakes('del a', m.UndefinedName)
def test_globalFromNestedScope(self):
"""Global names are available from nested scopes."""
self.flakes('''
a = 1
def b():
def c():
a
''')
def test_laterRedefinedGlobalFromNestedScope(self):
"""
Test that referencing a local name that shadows a global, before it is
defined, generates a warning.
"""
self.flakes('''
a = 1
def fun():
a
a = 2
return a
''', m.UndefinedLocal)
def test_laterRedefinedGlobalFromNestedScope2(self):
"""
Test that referencing a local name in a nested scope that shadows a
global declared in an enclosing scope, before it is defined, generates
a warning.
"""
self.flakes('''
a = 1
def fun():
global a
def fun2():
a
a = 2
return a
''', m.UndefinedLocal)
def test_intermediateClassScopeIgnored(self):
"""
If a name defined in an enclosing scope is shadowed by a local variable
and the name is used locally before it is bound, an unbound local
warning is emitted, even if there is a class scope between the enclosing
scope and the local scope.
"""
self.flakes('''
def f():
x = 1
class g:
def h(self):
a = x
x = None
print(x, a)
print(x)
''', m.UndefinedLocal)
def test_doubleNestingReportsClosestName(self):
"""
Test that referencing a local name in a nested scope that shadows a
variable declared in two different outer scopes before it is defined
in the innermost scope generates an UnboundLocal warning which
refers to the nearest shadowed name.
"""
exc = self.flakes('''
def a():
x = 1
def b():
x = 2 # line 5
def c():
x
x = 3
return x
return x
return x
''', m.UndefinedLocal).messages[0]
self.assertEqual(exc.message_args, ('x', 5))
def test_laterRedefinedGlobalFromNestedScope3(self):
"""
Test that referencing a local name in a nested scope that shadows a
global, before it is defined, generates a warning.
"""
self.flakes('''
def fun():
a = 1
def fun2():
a
a = 1
return a
return a
''', m.UndefinedLocal)
def test_undefinedAugmentedAssignment(self):
self.flakes(
'''
def f(seq):
a = 0
seq[a] += 1
seq[b] /= 2
c[0] *= 2
a -= 3
d += 4
e[any] = 5
''',
m.UndefinedName, # b
m.UndefinedName, # c
m.UndefinedName, m.UnusedVariable, # d
m.UndefinedName, # e
)
def test_nestedClass(self):
"""Nested classes can access enclosing scope."""
self.flakes('''
def f(foo):
class C:
bar = foo
def f(self):
return foo
return C()
f(123).f()
''')
def test_badNestedClass(self):
"""Free variables in nested classes must bind at class creation."""
self.flakes('''
def f():
class C:
bar = foo
foo = 456
return foo
f()
''', m.UndefinedName)
def test_definedAsStarArgs(self):
"""Star and double-star arg names are defined."""
self.flakes('''
def f(a, *b, **c):
print(a, b, c)
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_definedAsStarUnpack(self):
"""Star names in unpack are defined."""
self.flakes('''
a, *b = range(10)
print(a, b)
''')
self.flakes('''
*a, b = range(10)
print(a, b)
''')
self.flakes('''
a, *b, c = range(10)
print(a, b, c)
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_usedAsStarUnpack(self):
"""
Star names in unpack are used if RHS is not a tuple/list literal.
"""
self.flakes('''
def f():
a, *b = range(10)
''')
self.flakes('''
def f():
(*a, b) = range(10)
''')
self.flakes('''
def f():
[a, *b, c] = range(10)
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_unusedAsStarUnpack(self):
"""
Star names in unpack are unused if RHS is a tuple/list literal.
"""
self.flakes('''
def f():
a, *b = any, all, 4, 2, 'un'
''', m.UnusedVariable, m.UnusedVariable)
self.flakes('''
def f():
(*a, b) = [bool, int, float, complex]
''', m.UnusedVariable, m.UnusedVariable)
self.flakes('''
def f():
[a, *b, c] = 9, 8, 7, 6, 5, 4
''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable)
@skipIf(version_info < (3,), 'new in Python 3')
def test_keywordOnlyArgs(self):
"""Keyword-only arg names are defined."""
self.flakes('''
def f(*, a, b=None):
print(a, b)
''')
self.flakes('''
import default_b
def f(*, a, b=default_b):
print(a, b)
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_keywordOnlyArgsUndefined(self):
"""Typo in kwonly name."""
self.flakes('''
def f(*, a, b=default_c):
print(a, b)
''', m.UndefinedName)
@skipIf(version_info < (3,), 'new in Python 3')
def test_annotationUndefined(self):
"""Undefined annotations."""
self.flakes('''
from abc import note1, note2, note3, note4, note5
def func(a: note1, *args: note2,
b: note3=12, **kw: note4) -> note5: pass
''')
self.flakes('''
def func():
d = e = 42
def func(a: {1, d}) -> (lambda c: e): pass
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_metaClassUndefined(self):
self.flakes('''
from abc import ABCMeta
class A(metaclass=ABCMeta): pass
''')
def test_definedInGenExp(self):
"""
Using the loop variable of a generator expression results in no
warnings.
"""
self.flakes('(a for a in %srange(10) if a)' %
('x' if version_info < (3,) else ''))
def test_undefinedWithErrorHandler(self):
"""
Some compatibility code checks explicitly for NameError.
It should not trigger warnings.
"""
self.flakes('''
try:
socket_map
except NameError:
socket_map = {}
''')
self.flakes('''
try:
_memoryview.contiguous
except (NameError, AttributeError):
raise RuntimeError("Python >= 3.3 is required")
''')
# If NameError is not explicitly handled, generate a warning
self.flakes('''
try:
socket_map
except:
socket_map = {}
''', m.UndefinedName)
self.flakes('''
try:
socket_map
except Exception:
socket_map = {}
''', m.UndefinedName)
def test_definedInClass(self):
"""
Defined name for generator expressions and dict/set comprehension.
"""
self.flakes('''
class A:
T = range(10)
Z = (x for x in T)
L = [x for x in T]
B = dict((i, str(i)) for i in T)
''')
if version_info >= (2, 7):
self.flakes('''
class A:
T = range(10)
X = {x for x in T}
Y = {x:x for x in T}
''')
def test_undefinedInLoop(self):
"""
The loop variable is defined after the expression is computed.
"""
self.flakes('''
for i in range(i):
print(i)
''', m.UndefinedName)
self.flakes('''
[42 for i in range(i)]
''', m.UndefinedName)
self.flakes('''
(42 for i in range(i))
''', m.UndefinedName)
class NameTests(TestCase):
"""
Tests for some extra cases of name handling.
"""
def test_impossibleContext(self):
"""
A Name node with an unrecognized context results in a RuntimeError being
raised.
"""
tree = compile("x = 10", "<test>", "exec", PyCF_ONLY_AST)
# Make it into something unrecognizable.
tree.body[0].targets[0].ctx = object()
self.assertRaises(RuntimeError, checker.Checker, tree)

View File

@ -26,7 +26,7 @@
:license: BSD, see LICENSE for details.
"""
__version__ = '2.0.1'
__version__ = '2.0.2'
__docformat__ = 'restructuredtext'
__all__ = ['lex', 'format', 'highlight']
@ -45,7 +45,8 @@ def lex(code, lexer):
return lexer.get_tokens(code)
except TypeError as err:
if isinstance(err.args[0], str) and \
'unbound method get_tokens' in err.args[0]:
('unbound method get_tokens' in err.args[0] or
'missing 1 required positional argument' in err.args[0]):
raise TypeError('lex() argument must be a lexer instance, '
'not a class')
raise
@ -61,15 +62,15 @@ def format(tokens, formatter, outfile=None):
"""
try:
if not outfile:
#print formatter, 'using', formatter.encoding
realoutfile = formatter.encoding and BytesIO() or StringIO()
realoutfile = getattr(formatter, 'encoding', None) and BytesIO() or StringIO()
formatter.format(tokens, realoutfile)
return realoutfile.getvalue()
else:
formatter.format(tokens, outfile)
except TypeError as err:
if isinstance(err.args[0], str) and \
'unbound method format' in err.args[0]:
('unbound method format' in err.args[0] or
'missing 1 required positional argument' in err.args[0]):
raise TypeError('format() argument must be a formatter instance, '
'not a class')
raise
@ -86,6 +87,6 @@ def highlight(code, lexer, formatter, outfile=None):
return format(lex(code, lexer), formatter, outfile)
if __name__ == '__main__':
if __name__ == '__main__': # pragma: no cover
from pygments.cmdline import main
sys.exit(main(sys.argv))

View File

@ -198,19 +198,7 @@ def _print_list(what):
print(" %s" % docstring_headline(cls))
def main(args=sys.argv):
"""
Main command line entry point.
"""
# pylint: disable-msg=R0911,R0912,R0915
usage = USAGE % ((args[0],) * 6)
try:
popts, args = getopt.getopt(args[1:], "l:f:F:o:O:P:LS:a:N:hVHgs")
except getopt.GetoptError:
print(usage, file=sys.stderr)
return 2
def main_inner(popts, args, usage):
opts = {}
O_opts = []
P_opts = []
@ -331,10 +319,13 @@ def main(args=sys.argv):
opts.pop('-F', None)
# select lexer
lexer = opts.pop('-l', None)
if lexer:
lexer = None
# given by name?
lexername = opts.pop('-l', None)
if lexername:
try:
lexer = get_lexer_by_name(lexer, **parsed_opts)
lexer = get_lexer_by_name(lexername, **parsed_opts)
except (OptionError, ClassNotFound) as err:
print('Error:', err, file=sys.stderr)
return 1
@ -441,8 +432,8 @@ def main(args=sys.argv):
fmter.name in ('Terminal', 'Terminal256'):
# unfortunately colorama doesn't support binary streams on Py3
if sys.version_info > (3,):
import io
outfile = io.TextIOWrapper(outfile, encoding=fmter.encoding)
from pygments.util import UnclosingTextIOWrapper
outfile = UnclosingTextIOWrapper(outfile, encoding=fmter.encoding)
fmter.encoding = None
try:
import colorama.initialise
@ -461,40 +452,58 @@ def main(args=sys.argv):
right = escapeinside[1]
lexer = LatexEmbeddedLexer(left, right, lexer)
# ... and do it!
try:
# process filters
for fname, fopts in F_opts:
# process filters
for fname, fopts in F_opts:
try:
lexer.add_filter(fname, **fopts)
except ClassNotFound as err:
print('Error:', err, file=sys.stderr)
return 1
if '-s' not in opts:
# process whole input as per normal...
highlight(code, lexer, fmter, outfile)
else:
if not lexer:
print('Error: when using -s a lexer has to be selected with -l',
file=sys.stderr)
return 1
# line by line processing of stdin (eg: for 'tail -f')...
try:
while 1:
if sys.version_info > (3,):
# Python 3: we have to use .buffer to get a binary stream
line = sys.stdin.buffer.readline()
else:
line = sys.stdin.readline()
if not line:
break
if not inencoding:
line = guess_decode_from_terminal(line, sys.stdin)[0]
highlight(line, lexer, fmter, outfile)
if hasattr(outfile, 'flush'):
outfile.flush()
except KeyboardInterrupt:
return 0
# ... and do it!
if '-s' not in opts:
# process whole input as per normal...
highlight(code, lexer, fmter, outfile)
return 0
else:
if not lexer:
print('Error: when using -s a lexer has to be selected with -l',
file=sys.stderr)
return 1
# line by line processing of stdin (eg: for 'tail -f')...
try:
while 1:
if sys.version_info > (3,):
# Python 3: we have to use .buffer to get a binary stream
line = sys.stdin.buffer.readline()
else:
line = sys.stdin.readline()
if not line:
break
if not inencoding:
line = guess_decode_from_terminal(line, sys.stdin)[0]
highlight(line, lexer, fmter, outfile)
if hasattr(outfile, 'flush'):
outfile.flush()
except KeyboardInterrupt:
return 0
def main(args=sys.argv):
"""
Main command line entry point.
"""
usage = USAGE % ((args[0],) * 6)
try:
popts, args = getopt.getopt(args[1:], "l:f:F:o:O:P:LS:a:N:hVHgs")
except getopt.GetoptError:
print(usage, file=sys.stderr)
return 2
try:
return main_inner(popts, args, usage)
except Exception:
raise
import traceback
info = traceback.format_exception(*sys.exc_info())
msg = info[-1].strip()
@ -505,5 +514,3 @@ def main(args=sys.argv):
print('*** Error while highlighting:', file=sys.stderr)
print(msg, file=sys.stderr)
return 1
return 0

View File

@ -75,7 +75,7 @@ def get_formatter_by_name(_alias, **options):
"""
cls = find_formatter_class(_alias)
if cls is None:
raise ClassNotFound("No formatter found for name %r" % _alias)
raise ClassNotFound("no formatter found for name %r" % _alias)
return cls(**options)
@ -95,7 +95,7 @@ def get_formatter_for_filename(fn, **options):
for filename in cls.filenames:
if _fn_matches(fn, filename):
return cls(**options)
raise ClassNotFound("No formatter found for file name %r" % fn)
raise ClassNotFound("no formatter found for file name %r" % fn)
class _automodule(types.ModuleType):

View File

@ -32,7 +32,8 @@ FORMATTERS = {
'TestcaseFormatter': ('pygments.formatters.other', 'Testcase', ('testcase',), (), 'Format tokens as appropriate for a new testcase.')
}
if __name__ == '__main__':
if __name__ == '__main__': # pragma: no cover
import sys
import os

File diff suppressed because one or more lines are too long

View File

@ -143,7 +143,8 @@ MODULES = {'basic': ('_G',
'table.remove',
'table.sort')}
if __name__ == '__main__':
if __name__ == '__main__': # pragma: no cover
import re
try:
from urllib import urlopen

View File

@ -67,7 +67,7 @@ LEXERS = {
'CheetahJavascriptLexer': ('pygments.lexers.templates', 'JavaScript+Cheetah', ('js+cheetah', 'javascript+cheetah', 'js+spitfire', 'javascript+spitfire'), (), ('application/x-javascript+cheetah', 'text/x-javascript+cheetah', 'text/javascript+cheetah', 'application/x-javascript+spitfire', 'text/x-javascript+spitfire', 'text/javascript+spitfire')),
'CheetahLexer': ('pygments.lexers.templates', 'Cheetah', ('cheetah', 'spitfire'), ('*.tmpl', '*.spt'), ('application/x-cheetah', 'application/x-spitfire')),
'CheetahXmlLexer': ('pygments.lexers.templates', 'XML+Cheetah', ('xml+cheetah', 'xml+spitfire'), (), ('application/xml+cheetah', 'application/xml+spitfire')),
'CirruLexer': ('pygments.lexers.webmisc', 'Cirru', ('cirru',), ('*.cirru', '*.cr'), ('text/x-cirru',)),
'CirruLexer': ('pygments.lexers.webmisc', 'Cirru', ('cirru',), ('*.cirru',), ('text/x-cirru',)),
'ClayLexer': ('pygments.lexers.c_like', 'Clay', ('clay',), ('*.clay',), ('text/x-clay',)),
'ClojureLexer': ('pygments.lexers.jvm', 'Clojure', ('clojure', 'clj'), ('*.clj',), ('text/x-clojure', 'application/x-clojure')),
'ClojureScriptLexer': ('pygments.lexers.jvm', 'ClojureScript', ('clojurescript', 'cljs'), ('*.cljs',), ('text/x-clojurescript', 'application/x-clojurescript')),
@ -372,7 +372,7 @@ LEXERS = {
'ZephirLexer': ('pygments.lexers.php', 'Zephir', ('zephir',), ('*.zep',), ()),
}
if __name__ == '__main__':
if __name__ == '__main__': # pragma: no cover
import sys
import os

View File

@ -4672,7 +4672,8 @@ MODULES = {'.NET': ('dotnet_load',),
'xdiff_string_patch',
'xdiff_string_rabdiff')}
if __name__ == '__main__':
if __name__ == '__main__': # pragma: no cover
import glob
import os
import pprint

View File

@ -507,7 +507,8 @@ PLPGSQL_KEYWORDS = (
'RETURN', 'REVERSE', 'SQLSTATE', 'WHILE',
)
if __name__ == '__main__':
if __name__ == '__main__': # pragma: no cover
import re
try:
from urllib import urlopen

View File

@ -3051,7 +3051,8 @@ variables_kw = (
'xcoslib',
)
if __name__ == '__main__':
if __name__ == '__main__': # pragma: no cover
import subprocess
from pygments.util import format_lines, duplicates_removed

View File

@ -1091,7 +1091,9 @@ FUNCTIONS = (
'SDKCall',
'GetPlayerResourceEntity',
)
if __name__ == '__main__':
if __name__ == '__main__': # pragma: no cover
import re
import sys
try:

View File

@ -18,7 +18,7 @@ __all__ = ['VCTreeStatusLexer', 'PyPyLogLexer']
class VCTreeStatusLexer(RegexLexer):
"""
For colorizing output of version control status commans, like "hg
For colorizing output of version control status commands, like "hg
status" or "svn status".
.. versionadded:: 2.0

View File

@ -497,7 +497,7 @@ class GenericAspxLexer(RegexLexer):
# TODO support multiple languages within the same source file
class CSharpAspxLexer(DelegatingLexer):
"""
Lexer for highligting C# within ASP.NET pages.
Lexer for highlighting C# within ASP.NET pages.
"""
name = 'aspx-cs'
@ -518,7 +518,7 @@ class CSharpAspxLexer(DelegatingLexer):
class VbNetAspxLexer(DelegatingLexer):
"""
Lexer for highligting Visual Basic.net within ASP.NET pages.
Lexer for highlighting Visual Basic.net within ASP.NET pages.
"""
name = 'aspx-vb'
@ -623,6 +623,7 @@ class FSharpLexer(RegexLexer):
(r'\b(member|override)(\s+)(\w+)(\.)(\w+)',
bygroups(Keyword, Text, Name, Punctuation, Name.Function)),
(r'\b(%s)\b' % '|'.join(keywords), Keyword),
(r'``([^`\n\r\t]|`[^`\n\r\t])+``', Name),
(r'(%s)' % '|'.join(keyopts), Operator),
(r'(%s|%s)?%s' % (infix_syms, prefix_syms, operators), Operator),
(r'\b(%s)\b' % '|'.join(word_operators), Operator.Word),

View File

@ -213,7 +213,7 @@ class LiveScriptLexer(RegexLexer):
.. _LiveScript: http://gkz.github.com/LiveScript/
New in Pygments 1.6.
.. versionadded:: 1.6
"""
name = 'LiveScript'

View File

@ -279,6 +279,7 @@ class LogosLexer(ObjectiveCppLexer):
return 1.0
return 0
class SwiftLexer(RegexLexer):
"""
For `Swift <https://developer.apple.com/swift/>`_ source.
@ -303,84 +304,89 @@ class SwiftLexer(RegexLexer):
include('keywords'),
# Global Types
(r'(Array|AutoreleasingUnsafeMutablePointer|BidirectionalReverseView'
r'|Bit|Bool|CFunctionPointer|COpaquePointer|CVaListPointer'
r'|Character|ClosedInterval|CollectionOfOne|ContiguousArray'
r'|Dictionary|DictionaryGenerator|DictionaryIndex|Double'
r'|EmptyCollection|EmptyGenerator|EnumerateGenerator'
r'|EnumerateSequence|FilterCollectionView'
r'|FilterCollectionViewIndex|FilterGenerator|FilterSequenceView'
r'|Float|Float80|FloatingPointClassification|GeneratorOf'
r'|GeneratorOfOne|GeneratorSequence|HalfOpenInterval|HeapBuffer'
r'|HeapBufferStorage|ImplicitlyUnwrappedOptional|IndexingGenerator'
r'|Int|Int16|Int32|Int64|Int8|LazyBidirectionalCollection'
r'|LazyForwardCollection|LazyRandomAccessCollection'
r'|LazySequence|MapCollectionView|MapSequenceGenerator'
r'|MapSequenceView|MirrorDisposition|ObjectIdentifier|OnHeap'
r'|Optional|PermutationGenerator|QuickLookObject'
r'|RandomAccessReverseView|Range|RangeGenerator|RawByte|Repeat'
r'|ReverseBidirectionalIndex|ReverseRandomAccessIndex|SequenceOf'
r'|SinkOf|Slice|StaticString|StrideThrough|StrideThroughGenerator'
r'|StrideTo|StrideToGenerator|String|UInt|UInt16|UInt32|UInt64'
r'|UInt8|UTF16|UTF32|UTF8|UnicodeDecodingResult|UnicodeScalar'
r'|Unmanaged|UnsafeBufferPointer|UnsafeBufferPointerGenerator'
r'|UnsafeMutableBufferPointer|UnsafeMutablePointer|UnsafePointer'
r'|Zip2|ZipGenerator2'
# Protocols
r'|AbsoluteValuable|AnyObject|ArrayLiteralConvertible'
r'|BidirectionalIndexType|BitwiseOperationsType'
r'|BooleanLiteralConvertible|BooleanType|CVarArgType'
r'|CollectionType|Comparable|DebugPrintable'
r'|DictionaryLiteralConvertible|Equatable'
r'|ExtendedGraphemeClusterLiteralConvertible'
r'|ExtensibleCollectionType|FloatLiteralConvertible'
r'|FloatingPointType|ForwardIndexType|GeneratorType|Hashable'
r'|IntegerArithmeticType|IntegerLiteralConvertible|IntegerType'
r'|IntervalType|MirrorType|MutableCollectionType|MutableSliceable'
r'|NilLiteralConvertible|OutputStreamType|Printable'
r'|RandomAccessIndexType|RangeReplaceableCollectionType'
r'|RawOptionSetType|RawRepresentable|Reflectable|SequenceType'
r'|SignedIntegerType|SignedNumberType|SinkType|Sliceable'
r'|Streamable|Strideable|StringInterpolationConvertible'
r'|StringLiteralConvertible|UnicodeCodecType'
r'|UnicodeScalarLiteralConvertible|UnsignedIntegerType'
r'|_ArrayBufferType|_BidirectionalIndexType|_CocoaStringType'
r'|_CollectionType|_Comparable|_ExtensibleCollectionType'
r'|_ForwardIndexType|_Incrementable|_IntegerArithmeticType'
r'|_IntegerType|_ObjectiveCBridgeable|_RandomAccessIndexType'
r'|_RawOptionSetType|_SequenceType|_Sequence_Type'
r'|_SignedIntegerType|_SignedNumberType|_Sliceable|_Strideable'
r'|_SwiftNSArrayRequiredOverridesType|_SwiftNSArrayType'
r'|_SwiftNSCopyingType|_SwiftNSDictionaryRequiredOverridesType'
r'|_SwiftNSDictionaryType|_SwiftNSEnumeratorType'
r'|_SwiftNSFastEnumerationType|_SwiftNSStringRequiredOverridesType'
r'|_SwiftNSStringType|_UnsignedIntegerType'
# Variables
r'|C_ARGC|C_ARGV|Process'
# Typealiases
r'|Any|AnyClass|BooleanLiteralType|CBool|CChar|CChar16|CChar32'
r'|CDouble|CFloat|CInt|CLong|CLongLong|CShort|CSignedChar'
r'|CUnsignedInt|CUnsignedLong|CUnsignedShort|CWideChar'
r'|ExtendedGraphemeClusterType|Float32|Float64|FloatLiteralType'
r'|IntMax|IntegerLiteralType|StringLiteralType|UIntMax|UWord'
r'|UnicodeScalarType|Void|Word'
# Foundation/Cocoa
r'|NSErrorPointer|NSObjectProtocol|Selector)\b', Name.Builtin),
(words((
'Array', 'AutoreleasingUnsafeMutablePointer', 'BidirectionalReverseView',
'Bit', 'Bool', 'CFunctionPointer', 'COpaquePointer', 'CVaListPointer',
'Character', 'ClosedInterval', 'CollectionOfOne', 'ContiguousArray',
'Dictionary', 'DictionaryGenerator', 'DictionaryIndex', 'Double',
'EmptyCollection', 'EmptyGenerator', 'EnumerateGenerator',
'EnumerateSequence', 'FilterCollectionView',
'FilterCollectionViewIndex', 'FilterGenerator', 'FilterSequenceView',
'Float', 'Float80', 'FloatingPointClassification', 'GeneratorOf',
'GeneratorOfOne', 'GeneratorSequence', 'HalfOpenInterval', 'HeapBuffer',
'HeapBufferStorage', 'ImplicitlyUnwrappedOptional', 'IndexingGenerator',
'Int', 'Int16', 'Int32', 'Int64', 'Int8', 'LazyBidirectionalCollection',
'LazyForwardCollection', 'LazyRandomAccessCollection',
'LazySequence', 'MapCollectionView', 'MapSequenceGenerator',
'MapSequenceView', 'MirrorDisposition', 'ObjectIdentifier', 'OnHeap',
'Optional', 'PermutationGenerator', 'QuickLookObject',
'RandomAccessReverseView', 'Range', 'RangeGenerator', 'RawByte', 'Repeat',
'ReverseBidirectionalIndex', 'ReverseRandomAccessIndex', 'SequenceOf',
'SinkOf', 'Slice', 'StaticString', 'StrideThrough', 'StrideThroughGenerator',
'StrideTo', 'StrideToGenerator', 'String', 'UInt', 'UInt16', 'UInt32',
'UInt64', 'UInt8', 'UTF16', 'UTF32', 'UTF8', 'UnicodeDecodingResult',
'UnicodeScalar', 'Unmanaged', 'UnsafeBufferPointer',
'UnsafeBufferPointerGenerator', 'UnsafeMutableBufferPointer',
'UnsafeMutablePointer', 'UnsafePointer', 'Zip2', 'ZipGenerator2',
# Protocols
'AbsoluteValuable', 'AnyObject', 'ArrayLiteralConvertible',
'BidirectionalIndexType', 'BitwiseOperationsType',
'BooleanLiteralConvertible', 'BooleanType', 'CVarArgType',
'CollectionType', 'Comparable', 'DebugPrintable',
'DictionaryLiteralConvertible', 'Equatable',
'ExtendedGraphemeClusterLiteralConvertible',
'ExtensibleCollectionType', 'FloatLiteralConvertible',
'FloatingPointType', 'ForwardIndexType', 'GeneratorType', 'Hashable',
'IntegerArithmeticType', 'IntegerLiteralConvertible', 'IntegerType',
'IntervalType', 'MirrorType', 'MutableCollectionType', 'MutableSliceable',
'NilLiteralConvertible', 'OutputStreamType', 'Printable',
'RandomAccessIndexType', 'RangeReplaceableCollectionType',
'RawOptionSetType', 'RawRepresentable', 'Reflectable', 'SequenceType',
'SignedIntegerType', 'SignedNumberType', 'SinkType', 'Sliceable',
'Streamable', 'Strideable', 'StringInterpolationConvertible',
'StringLiteralConvertible', 'UnicodeCodecType',
'UnicodeScalarLiteralConvertible', 'UnsignedIntegerType',
'_ArrayBufferType', '_BidirectionalIndexType', '_CocoaStringType',
'_CollectionType', '_Comparable', '_ExtensibleCollectionType',
'_ForwardIndexType', '_Incrementable', '_IntegerArithmeticType',
'_IntegerType', '_ObjectiveCBridgeable', '_RandomAccessIndexType',
'_RawOptionSetType', '_SequenceType', '_Sequence_Type',
'_SignedIntegerType', '_SignedNumberType', '_Sliceable', '_Strideable',
'_SwiftNSArrayRequiredOverridesType', '_SwiftNSArrayType',
'_SwiftNSCopyingType', '_SwiftNSDictionaryRequiredOverridesType',
'_SwiftNSDictionaryType', '_SwiftNSEnumeratorType',
'_SwiftNSFastEnumerationType', '_SwiftNSStringRequiredOverridesType',
'_SwiftNSStringType', '_UnsignedIntegerType',
# Variables
'C_ARGC', 'C_ARGV', 'Process',
# Typealiases
'Any', 'AnyClass', 'BooleanLiteralType', 'CBool', 'CChar', 'CChar16',
'CChar32', 'CDouble', 'CFloat', 'CInt', 'CLong', 'CLongLong', 'CShort',
'CSignedChar', 'CUnsignedInt', 'CUnsignedLong', 'CUnsignedShort',
'CWideChar', 'ExtendedGraphemeClusterType', 'Float32', 'Float64',
'FloatLiteralType', 'IntMax', 'IntegerLiteralType', 'StringLiteralType',
'UIntMax', 'UWord', 'UnicodeScalarType', 'Void', 'Word',
# Foundation/Cocoa
'NSErrorPointer', 'NSObjectProtocol', 'Selector'), suffix=r'\b'),
Name.Builtin),
# Functions
(r'(abs|advance|alignof|alignofValue|assert|assertionFailure'
r'|contains|count|countElements|debugPrint|debugPrintln|distance'
r'|dropFirst|dropLast|dump|enumerate|equal|extend|fatalError'
r'|filter|find|first|getVaList|indices|insert|isEmpty|join|last'
r'|lazy|lexicographicalCompare|map|max|maxElement|min|minElement'
r'|numericCast|overlaps|partition|precondition|preconditionFailure'
r'|prefix|print|println|reduce|reflect|removeAll|removeAtIndex'
r'|removeLast|removeRange|reverse|sizeof|sizeofValue|sort|sorted'
r'|splice|split|startsWith|stride|strideof|strideofValue|suffix'
r'|swap|toDebugString|toString|transcode|underestimateCount'
r'|unsafeAddressOf|unsafeBitCast|unsafeDowncast'
r'|withExtendedLifetime|withUnsafeMutablePointer'
r'|withUnsafeMutablePointers|withUnsafePointer|withUnsafePointers'
r'|withVaList)\b', Name.Builtin.Pseudo),
(words((
'abs', 'advance', 'alignof', 'alignofValue', 'assert', 'assertionFailure',
'contains', 'count', 'countElements', 'debugPrint', 'debugPrintln',
'distance', 'dropFirst', 'dropLast', 'dump', 'enumerate', 'equal',
'extend', 'fatalError', 'filter', 'find', 'first', 'getVaList', 'indices',
'insert', 'isEmpty', 'join', 'last', 'lazy', 'lexicographicalCompare',
'map', 'max', 'maxElement', 'min', 'minElement', 'numericCast', 'overlaps',
'partition', 'precondition', 'preconditionFailure', 'prefix', 'print',
'println', 'reduce', 'reflect', 'removeAll', 'removeAtIndex', 'removeLast',
'removeRange', 'reverse', 'sizeof', 'sizeofValue', 'sort', 'sorted',
'splice', 'split', 'startsWith', 'stride', 'strideof', 'strideofValue',
'suffix', 'swap', 'toDebugString', 'toString', 'transcode',
'underestimateCount', 'unsafeAddressOf', 'unsafeBitCast', 'unsafeDowncast',
'withExtendedLifetime', 'withUnsafeMutablePointer',
'withUnsafeMutablePointers', 'withUnsafePointer', 'withUnsafePointers',
'withVaList'), suffix=r'\b'),
Name.Builtin.Pseudo),
# Implicit Block Variables
(r'\$\d+', Name.Variable),
@ -406,16 +412,22 @@ class SwiftLexer(RegexLexer):
(r'[a-zA-Z_]\w*', Name)
],
'keywords': [
(r'(break|case|continue|default|do|else|fallthrough|for|if|in'
r'|return|switch|where|while)\b', Keyword),
(words((
'break', 'case', 'continue', 'default', 'do', 'else',
'fallthrough', 'for', 'if', 'in', 'return', 'switch', 'where',
'while'), suffix=r'\b'),
Keyword),
(r'@availability\([^)]+\)', Keyword.Reserved),
(r'(associativity|convenience|dynamic|didSet|final|get|infix|inout'
r'|lazy|left|mutating|none|nonmutating|optional|override|postfix'
r'|precedence|prefix|Protocol|required|right|set|Type|unowned|weak'
r'|willSet|@(availability|autoclosure|noreturn'
r'|NSApplicationMain|NSCopying|NSManaged|objc'
r'|UIApplicationMain|IBAction|IBDesignable|IBInspectable'
r'|IBOutlet))\b',Keyword.Reserved),
(words((
'associativity', 'convenience', 'dynamic', 'didSet', 'final',
'get', 'infix', 'inout', 'lazy', 'left', 'mutating', 'none',
'nonmutating', 'optional', 'override', 'postfix', 'precedence',
'prefix', 'Protocol', 'required', 'right', 'set', 'Type',
'unowned', 'weak', 'willSet', '@availability', '@autoclosure',
'@noreturn', '@NSApplicationMain', '@NSCopying', '@NSManaged',
'@objc', '@UIApplicationMain', '@IBAction', '@IBDesignable',
'@IBInspectable', '@IBOutlet'), suffix=r'\b'),
Keyword.Reserved),
(r'(as|dynamicType|false|is|nil|self|Self|super|true|__COLUMN__'
r'|__FILE__|__FUNCTION__|__LINE__|_)\b', Keyword.Constant),
(r'import\b', Keyword.Declaration, 'module'),
@ -425,9 +437,11 @@ class SwiftLexer(RegexLexer):
bygroups(Keyword.Declaration, Text, Name.Function)),
(r'(var|let)(\s+)([a-zA-Z_]\w*)', bygroups(Keyword.Declaration,
Text, Name.Variable)),
(r'(class|deinit|enum|extension|func|import|init|internal|let'
r'|operator|private|protocol|public|static|struct|subscript'
r'|typealias|var)\b', Keyword.Declaration)
(words((
'class', 'deinit', 'enum', 'extension', 'func', 'import', 'init',
'internal', 'let', 'operator', 'private', 'protocol', 'public',
'static', 'struct', 'subscript', 'typealias', 'var'), suffix=r'\b'),
Keyword.Declaration)
],
'comment': [
(r':param: [a-zA-Z_]\w*|:returns?:|(FIXME|MARK|TODO):',

View File

@ -378,6 +378,7 @@ class PythonConsoleLexer(Lexer):
tb = 0
for i, t, v in tblexer.get_tokens_unprocessed(curtb):
yield tbindex+i, t, v
curtb = ''
else:
yield match.start(), Generic.Output, line
if curcode:

View File

@ -1122,7 +1122,7 @@ class HtmlPhpLexer(DelegatingLexer):
class XmlPhpLexer(DelegatingLexer):
"""
Subclass of `PhpLexer` that higlights unhandled data with the `XmlLexer`.
Subclass of `PhpLexer` that highlights unhandled data with the `XmlLexer`.
"""
name = 'XML+PHP'
@ -1180,7 +1180,7 @@ class JavascriptPhpLexer(DelegatingLexer):
class HtmlSmartyLexer(DelegatingLexer):
"""
Subclass of the `SmartyLexer` that highighlights unlexed data with the
Subclass of the `SmartyLexer` that highlights unlexed data with the
`HtmlLexer`.
Nested Javascript and CSS is highlighted too.
@ -1263,7 +1263,7 @@ class JavascriptSmartyLexer(DelegatingLexer):
class HtmlDjangoLexer(DelegatingLexer):
"""
Subclass of the `DjangoLexer` that highighlights unlexed data with the
Subclass of the `DjangoLexer` that highlights unlexed data with the
`HtmlLexer`.
Nested Javascript and CSS is highlighted too.
@ -1851,7 +1851,7 @@ class HandlebarsHtmlLexer(DelegatingLexer):
class YamlJinjaLexer(DelegatingLexer):
"""
Subclass of the `DjangoLexer` that highighlights unlexed data with the
Subclass of the `DjangoLexer` that highlights unlexed data with the
`YamlLexer`.
Commonly used in Saltstack salt states.

View File

@ -132,7 +132,7 @@ def allexcept(*args):
return u''.join(globals()[cat] for cat in newcats)
def _handle_runs(char_list):
def _handle_runs(char_list): # pragma: no cover
buf = []
for c in char_list:
if len(c) == 1:
@ -149,7 +149,7 @@ def _handle_runs(char_list):
yield u'%s-%s' % (a, b)
if __name__ == '__main__':
if __name__ == '__main__': # pragma: no cover
import unicodedata
# we need Py3 for the determination of the XID_* properties

View File

@ -55,7 +55,7 @@ def get_bool_opt(options, optname, default=None):
elif not isinstance(string, string_types):
raise OptionError('Invalid type %r for option %s; use '
'1/0, yes/no, true/false, on/off' % (
string, optname))
string, optname))
elif string.lower() in ('1', 'yes', 'true', 'on'):
return True
elif string.lower() in ('0', 'no', 'false', 'off'):
@ -63,7 +63,7 @@ def get_bool_opt(options, optname, default=None):
else:
raise OptionError('Invalid value %r for option %s; use '
'1/0, yes/no, true/false, on/off' % (
string, optname))
string, optname))
def get_int_opt(options, optname, default=None):
@ -73,11 +73,11 @@ def get_int_opt(options, optname, default=None):
except TypeError:
raise OptionError('Invalid type %r for option %s; you '
'must give an integer value' % (
string, optname))
string, optname))
except ValueError:
raise OptionError('Invalid value %r for option %s; you '
'must give an integer value' % (
string, optname))
string, optname))
def get_list_opt(options, optname, default=None):
@ -89,7 +89,7 @@ def get_list_opt(options, optname, default=None):
else:
raise OptionError('Invalid type %r for option %s; you '
'must give a list value' % (
val, optname))
val, optname))
def docstring_headline(obj):
@ -185,6 +185,8 @@ def html_doctype_matches(text):
_looks_like_xml_cache = {}
def looks_like_xml(text):
"""Check if a doctype exists or if we have some tags."""
if xml_decl_re.match(text):
@ -200,6 +202,7 @@ def looks_like_xml(text):
_looks_like_xml_cache[key] = rv
return rv
# Python narrow build compatibility
def _surrogatepair(c):
@ -210,6 +213,7 @@ def _surrogatepair(c):
# http://www.unicode.org/book/ch03.pdf
return (0xd7c0 + (c >> 10), (0xdc00 + (c & 0x3ff)))
def unirange(a, b):
"""Returns a regular expression string to match the given non-BMP range."""
if b < a:
@ -350,7 +354,8 @@ if sys.version_info < (3, 0):
u_prefix = 'u'
iteritems = dict.iteritems
itervalues = dict.itervalues
import StringIO, cStringIO
import StringIO
import cStringIO
# unfortunately, io.StringIO in Python 2 doesn't accept str at all
StringIO = StringIO.StringIO
BytesIO = cStringIO.StringIO
@ -362,7 +367,12 @@ else:
u_prefix = ''
iteritems = dict.items
itervalues = dict.values
from io import StringIO, BytesIO
from io import StringIO, BytesIO, TextIOWrapper
class UnclosingTextIOWrapper(TextIOWrapper):
# Don't close underlying buffer on destruction.
def close(self):
pass
def add_metaclass(metaclass):

View File

@ -8,4 +8,4 @@ a series of modes and panels that might be useful for any kind of code editor
widget, i.e. pyqode.core is a generic code editor widget.
"""
__version__ = '2.5.dev1'
__version__ = '2.5.0'

View File

@ -740,16 +740,24 @@ class CodeEdit(QtWidgets.QPlainTextEdit):
"""
cursor = self.textCursor()
assert isinstance(cursor, QtGui.QTextCursor)
cursor.beginEditBlock()
col = cursor.positionInBlock()
cursor.select(cursor.LineUnderCursor)
has_selection = True
if not cursor.hasSelection():
cursor.select(cursor.LineUnderCursor)
has_selection = False
line = cursor.selectedText()
cursor.movePosition(cursor.EndOfBlock)
cursor.insertText('\n' + line)
cursor.movePosition(cursor.StartOfBlock)
cursor.movePosition(cursor.Right, cursor.MoveAnchor, col)
self.setTextCursor(cursor)
line = '\n'.join(line.split('\u2029'))
end = cursor.selectionEnd()
cursor.setPosition(end)
cursor.beginEditBlock()
cursor.insertText('\n')
cursor.insertText(line)
cursor.endEditBlock()
if has_selection:
pos = cursor.position()
cursor.setPosition(end+1)
cursor.setPosition(pos, cursor.KeepAnchor)
self.setTextCursor(cursor)
@QtCore.Slot()
def indent(self):
@ -1170,7 +1178,7 @@ class CodeEdit(QtWidgets.QPlainTextEdit):
move = QtGui.QTextCursor.MoveAnchor
if select:
move = QtGui.QTextCursor.KeepAnchor
if delta:
if delta > 0:
cursor.movePosition(QtGui.QTextCursor.Left, move, delta)
else:
cursor.movePosition(QtGui.QTextCursor.StartOfBlock, move)

View File

@ -27,6 +27,9 @@ class BackendManager(Manager):
- send_request
"""
LAST_PORT = None
LAST_PROCESS = None
SHARE_COUNT = 0
def __init__(self, editor):
super(BackendManager, self).__init__(editor)
@ -35,6 +38,7 @@ class BackendManager(Manager):
self.server_script = None
self.interpreter = None
self.args = None
self._shared = False
@staticmethod
def pick_free_port():
@ -45,7 +49,8 @@ class BackendManager(Manager):
test_socket.close()
return free_port
def start(self, script, interpreter=sys.executable, args=None, error_callback=None):
def start(self, script, interpreter=sys.executable, args=None,
error_callback=None, reuse=False):
"""
Starts the backend process.
@ -65,29 +70,46 @@ class BackendManager(Manager):
application (frozen backends do not require an interpreter).
:param args: list of additional command line args to use to start
the backend process.
:param reuse: True to reuse an existing backend process. WARNING: to
use this, your application must have one single server script. If
you're creating an app which supports multiple programming languages
you will need to merge all backend scripts into one single script,
otherwise the wrong script might be picked up).
"""
if self.running:
self.stop()
self.server_script = script
self.interpreter = interpreter
self.args = args
backend_script = script.replace('.pyc', '.py')
self._port = self.pick_free_port()
if hasattr(sys, "frozen") and not backend_script.endswith('.py'):
# frozen backend script on windows/mac does not need an interpreter
program = backend_script
pgm_args = [str(self._port)]
self._shared = reuse
if reuse and BackendManager.SHARE_COUNT:
self._port = BackendManager.LAST_PORT
self._process = BackendManager.LAST_PROCESS
BackendManager.SHARE_COUNT += 1
else:
program = interpreter
pgm_args = [backend_script, str(self._port)]
if args:
pgm_args += args
self._process = BackendProcess(self.editor)
if error_callback:
self._process.error.connect(error_callback)
self._process.start(program, pgm_args)
_logger().info('starting backend process: %s %s', program,
' '.join(pgm_args))
if self.running:
self.stop()
self.server_script = script
self.interpreter = interpreter
self.args = args
backend_script = script.replace('.pyc', '.py')
self._port = self.pick_free_port()
if hasattr(sys, "frozen") and not backend_script.endswith('.py'):
# frozen backend script on windows/mac does not need an
# interpreter
program = backend_script
pgm_args = [str(self._port)]
else:
program = interpreter
pgm_args = [backend_script, str(self._port)]
if args:
pgm_args += args
self._process = BackendProcess(self.editor)
if error_callback:
self._process.error.connect(error_callback)
self._process.start(program, pgm_args)
if reuse:
BackendManager.LAST_PROCESS = self._process
BackendManager.LAST_PORT = self._port
BackendManager.SHARE_COUNT += 1
_logger().info('starting backend process: %s %s', program,
' '.join(pgm_args))
def stop(self):
"""
@ -95,6 +117,10 @@ class BackendManager(Manager):
"""
if self._process is None:
return
if self._shared:
BackendManager.SHARE_COUNT -= 1
if BackendManager.SHARE_COUNT:
return
_logger().debug('stopping backend process')
# close all sockets
for socket in self._sockets:

View File

@ -17,6 +17,7 @@ from .filewatcher import FileWatcherMode
from .indenter import IndenterMode
from .matcher import SymbolMatcherMode
from .occurences import OccurrencesHighlighterMode
from .outline import OutlineMode
from .right_margin import RightMarginMode
from .pygments_sh import PygmentsSH
from .wordclick import WordClickMode
@ -39,6 +40,7 @@ __all__ = [
'FileWatcherMode',
'IndenterMode',
'OccurrencesHighlighterMode',
'OutlineMode',
'PygmentsSH',
'PygmentsSyntaxHighlighter',
'PYGMENTS_STYLES',

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
""" Contains the AutoCompleteMode """
import logging
from pyqode.qt import QtCore
from pyqode.core.api import TextHelper
from pyqode.core.api.mode import Mode
@ -71,10 +72,29 @@ class AutoCompleteMode(Mode):
next_char = TextHelper(self.editor).get_right_character()
self.logger.debug('next char: %s', next_char)
ignore = False
if txt and next_char == txt and next_char in self.MAPPING:
if event.key() == QtCore.Qt.Key_Backspace:
# get the character that will get deleted
tc = self.editor.textCursor()
pos = tc.position()
tc.movePosition(tc.Left)
tc.movePosition(tc.Right, tc.KeepAnchor)
del_char = tc.selectedText()
if del_char in self.MAPPING and self.MAPPING[del_char] == next_char:
tc.beginEditBlock()
tc.movePosition(tc.Right, tc.KeepAnchor)
tc.insertText('')
tc.setPosition(pos - 2)
tc.endEditBlock()
self.editor.setTextCursor(tc)
ignore = True
elif txt and next_char == txt and next_char in self.MAPPING:
ignore = True
elif event.text() == ')' or event.text() == ']' or event.text() == '}':
if next_char == ')' or next_char == ']' or next_char == '}':
# if typing the same symbol twice, the symbol should not be written
# and the cursor moved just after the char
# e.g. if you type ) just before ), the cursor will just move after
# the existing )
if next_char == event.text():
ignore = True
if ignore:
event.accept()

View File

@ -3,8 +3,10 @@
Contains the mode that control the external changes of file.
"""
import os
from pyqode.core.api import TextHelper
from pyqode.core.api.mode import Mode
from pyqode.qt import QtCore, QtWidgets
from pyqode.core.cache import Cache
class FileWatcherMode(Mode, QtCore.QObject):
@ -57,9 +59,6 @@ class FileWatcherMode(Mode, QtCore.QObject):
if state:
self.editor.new_text_set.connect(self._update_mtime)
self.editor.new_text_set.connect(self._timer.start)
# self.editor.text_saved.connect(self._update_mtime)
# self.editor.text_saved.connect(self._timer.start)
# self.editor.text_saving.connect(self._timer.stop)
self.editor.text_saving.connect(self._cancel_next_change)
self.editor.text_saved.connect(self._restart_monitoring)
self.editor.focused_in.connect(self._check_for_pending)
@ -67,9 +66,6 @@ class FileWatcherMode(Mode, QtCore.QObject):
self._timer.stop()
self.editor.new_text_set.connect(self._update_mtime)
self.editor.new_text_set.connect(self._timer.start)
# self.editor.text_saved.disconnect(self._update_mtime)
# self.editor.text_saved.disconnect(self._timer.start)
# self.editor.text_saving.disconnect(self._timer.stop)
self.editor.text_saving.disconnect(self._cancel_next_change)
self.editor.text_saved.disconnect(self._restart_monitoring)
self.editor.focused_in.disconnect(self._check_for_pending)
@ -147,6 +143,12 @@ class FileWatcherMode(Mode, QtCore.QObject):
"""
def inner_action(*args):
""" Inner action: open file """
# cache cursor position before reloading so that the cursor position
# is restored automatically after reload has finished.
# See OpenCobolIDE/OpenCobolIDE#97
Cache().set_cursor_position(
self.editor.file.path,
TextHelper(self.editor).cursor_position())
self.editor.file.open(self.editor.file.path)
self.file_reloaded.emit()

View File

@ -143,6 +143,12 @@ class OccurrencesHighlighterMode(Mode):
self._request_highlight()
def _on_results_available(self, results):
if len(results) > 500:
# limit number of results (on very big file where a lots of
# occurrences can be found, this would totally freeze the editor
# during a few seconds, with a limit of 500 we can make sure
# the editor will always remain responsive).
results = results[:500]
if len(results) > 1:
for start, end in results:
deco = TextDecoration(self.editor.textCursor(),

View File

@ -0,0 +1,79 @@
import logging
from pyqode.core.api import Mode
from pyqode.core.api import DelayJobRunner
from pyqode.core.backend import NotRunning
from pyqode.core.share import Definition
from pyqode.qt import QtCore
def _logger():
return logging.getLogger(__name__)
class OutlineMode(Mode, QtCore.QObject):
"""
Generic mode that provides outline information through the
document_changed signal and a specialised worker function.
To use this mode, you need to write a worker function that returns a list
of pyqode.core.share.Definition (see
pyqode.python.backend.workers.defined_names() for an example of how to
implement the worker function).
"""
#: Signal emitted when the document structure changed.
document_changed = QtCore.Signal()
@property
def definitions(self):
"""
Gets the list of top level definitions.
"""
return self._results
def __init__(self, worker, delay=1000):
Mode.__init__(self)
QtCore.QObject.__init__(self)
self._worker = worker
self._jobRunner = DelayJobRunner(delay=delay)
#: The list of definitions found in the file, each item is a
#: pyqode.core.share.Definition.
self._results = []
def on_state_changed(self, state):
if state:
self.editor.new_text_set.connect(self._run_analysis)
self.editor.textChanged.connect(self._request_analysis)
else:
self.editor.textChanged.disconnect(self._request_analysis)
self.editor.new_text_set.disconnect(self._run_analysis)
self._jobRunner.cancel_requests()
def _request_analysis(self):
self._jobRunner.request_job(self._run_analysis)
def _run_analysis(self):
if self.enabled and self.editor and self.editor.toPlainText() and \
self.editor.file:
request_data = {
'code': self.editor.toPlainText(),
'path': self.editor.file.path,
'encoding': self.editor.file.encoding
}
try:
self.editor.backend.send_request(
self._worker, request_data,
on_receive=self._on_results_available)
except NotRunning:
QtCore.QTimer.singleShot(100, self._run_analysis)
else:
self._results = []
self.document_changed.emit()
def _on_results_available(self, results):
if results:
results = [Definition.from_dict(ddict) for ddict in results]
self._results = results
if self._results is not None:
_logger().debug("Document structure changed")
self.document_changed.emit()

View File

@ -9,7 +9,7 @@ from pyqode.core.api import TextBlockHelper, folding, TextDecoration, \
DelayJobRunner
from pyqode.core.api.folding import FoldScope
from pyqode.core.api.panel import Panel
from pyqode.qt import QtCore, QtWidgets, QtGui
from pyqode.qt import QtCore, QtWidgets, QtGui, PYQT5_API
from pyqode.core.api.utils import TextHelper, drift_color, keep_tc_pos
@ -19,6 +19,7 @@ def _logger():
class FoldingPanel(Panel):
""" Displays the document outline and lets the user collapse/expand blocks.
The data represented by the panel come from the text block user state and
@ -356,7 +357,7 @@ class FoldingPanel(Panel):
rect = QtCore.QRect(0, top, self.sizeHint().width(),
self.sizeHint().height())
if self._native:
if os.environ['QT_API'].lower() != 'pyqt5':
if os.environ['QT_API'].lower() not in PYQT5_API:
opt = QtGui.QStyleOptionViewItemV2()
else:
opt = QtWidgets.QStyleOptionViewItem()

View File

@ -0,0 +1,60 @@
"""
This module contains some definition that can be shared
between the backend and the frontend (e.g. this module can be imported
without requiring any additional libraries).
"""
class Definition(object):
"""
Represents a definition in a source file.
This class is used by the OutlineExplorerWidget to show the defined names
in a source file.
Definition usually form a tree (a definition may have some child
definition, e.g. methods of a class).
"""
def __init__(self, name, line, column=0, icon=''):
#: Icon resource name associated with the definition, can be None
self.icon = icon
#: Definition name (name of the class, method, variable)
self.name = name
#: The line of the definition in the current editor text
self.line = line
#: The column of the definition in the current editor text
self.column = column
#: Possible list of children (only classes have children)
self.children = []
def add_child(self, definition):
"""
Adds a child definition
"""
self.children.append(definition)
def to_dict(self):
"""
Serializes a definition to a dictionary, ready for json.
Children are serialised recursively.
"""
ddict = {'name': self.name, 'icon': self.icon,
'line': self.line, 'column': self.column,
'children': []}
for child in self.children:
ddict['children'].append(child.to_dict())
return ddict
@staticmethod
def from_dict(ddict):
"""
Deserializes a definition from a simple dict.
"""
d = Definition(ddict['name'], ddict['line'], ddict['column'], ddict['icon'])
for child_dict in ddict['children']:
d.children.append(Definition.from_dict(child_dict))
return d
def __repr__(self):
return 'Definition(%r, %r, %r, %r)' % (self.name, self.line, self.column, self.icon)

View File

@ -12,6 +12,7 @@ pyqode applications:
- CodeEditTabWidget: tab widget made to handle CodeEdit instances (or
any other object that have the same interface).
- ErrorsTable: a QTableWidget specialised to show CheckerMessage.
- OutlineTreeWidget: a widget that show the outline of an editor.
"""
@ -25,6 +26,7 @@ from pyqode.core.widgets.menu_recents import RecentFilesManager
from pyqode.core.widgets.tabs import TabWidget
from pyqode.core.widgets.tab_bar import TabBar
from pyqode.core.widgets.prompt_line_edit import PromptLineEdit
from pyqode.core.widgets.outline import OutlineTreeWidget
from pyqode.core.widgets.splittable_tab_widget import (
SplittableTabWidget, SplittableCodeEditTabWidget)
from pyqode.core.widgets.filesystem_treeview import FileSystemTreeView
@ -46,6 +48,7 @@ __all__ = [
'TextCodeEdit',
'GenericCodeEdit',
'PromptLineEdit',
'OutlineTreeWidget',
'SplittableTabWidget',
'SplittableCodeEditTabWidget',
'TabBar'

View File

@ -35,11 +35,13 @@ class TextCodeEdit(CodeEdit):
def __init__(self, parent=None, server_script=server.__file__,
interpreter=sys.executable, args=None,
create_default_actions=True, color_scheme='qt'):
create_default_actions=True, color_scheme='qt',
reuse_backend=False):
from pyqode.core import panels
from pyqode.core import modes
super(TextCodeEdit, self).__init__(parent, create_default_actions)
self.backend.start(server_script, interpreter, args)
self.backend.start(server_script, interpreter, args,
reuse=reuse_backend)
# append panels
self.panels.append(panels.FoldingPanel())
@ -86,11 +88,13 @@ class GenericCodeEdit(CodeEdit):
def __init__(self, parent=None, server_script=server.__file__,
interpreter=sys.executable, args=None,
create_default_actions=True, color_scheme='qt'):
create_default_actions=True, color_scheme='qt',
reuse_backend=False):
super(GenericCodeEdit, self).__init__(parent, create_default_actions)
from pyqode.core import panels
from pyqode.core import modes
self.backend.start(server_script, interpreter, args)
self.backend.start(server_script, interpreter, args,
reuse=reuse_backend)
# append panels
self.panels.append(panels.FoldingPanel())
self.panels.append(panels.LineNumberPanel())

View File

@ -34,14 +34,13 @@ class FileSystemTreeView(QtWidgets.QTreeView):
Excludes :attr:`ignored_directories` and :attr:`ignored_extensions`
from the file system model.
"""
#: The list of directories to exclude
ignored_directories = ['__pycache__']
#: The list of file extension to exclude
ignored_extensions = ['.pyc', '.pyd', '.so', '.dll', '.exe',
'.egg-info', '.coverage', '.DS_Store']
def __init__(self):
super(FileSystemTreeView.FilterProxyModel, self).__init__()
#: The list of directories to ignore
self.ignored_directories = ['__pycache__']
#: The list of file extension to exclude
self.ignored_extensions = ['.pyc', '.pyd', '.so', '.dll', '.exe',
'.egg-info', '.coverage', '.DS_Store']
self._ignored_unused = []
def set_root_path(self, path):
@ -98,9 +97,10 @@ class FileSystemTreeView(QtWidgets.QTreeView):
self.customContextMenuRequested.connect(self._show_context_menu)
self.helper = FileSystemHelper(self)
self.setSelectionMode(self.ExtendedSelection)
self._ignored_extensions = []
self._ignored_directories = []
@classmethod
def ignore_directories(cls, *directories):
def ignore_directories(self, *directories):
"""
Adds the specified directories to the list of ignored directories.
@ -109,10 +109,9 @@ class FileSystemTreeView(QtWidgets.QTreeView):
:param directories: the directories to ignore
"""
for d in directories:
cls.FilterProxyModel.ignored_directories.append(d)
self._ignored_directories.append(d)
@classmethod
def ignore_extensions(cls, *extensions):
def ignore_extensions(self, *extensions):
"""
Adds the specified extensions to the list of ignored directories.
@ -123,7 +122,7 @@ class FileSystemTreeView(QtWidgets.QTreeView):
.. note:: extension must have the dot: '.py' and not 'py'
"""
for d in extensions:
cls.FilterProxyModel.ignored_extensions.append(d)
self._ignored_extensions.append(d)
def set_context_menu(self, context_menu):
"""
@ -146,6 +145,10 @@ class FileSystemTreeView(QtWidgets.QTreeView):
path = os.path.abspath(os.path.join(path, os.pardir))
self._fs_model_source = QtWidgets.QFileSystemModel()
self._fs_model_proxy = self.FilterProxyModel()
for item in self._ignored_directories:
self._fs_model_proxy.ignored_directories.append(item)
for item in self._ignored_extensions:
self._fs_model_proxy.ignored_extensions.append(item)
self._fs_model_proxy.setSourceModel(self._fs_model_source)
self.setModel(self._fs_model_proxy)
self._fs_model_proxy.set_root_path(path)
@ -519,7 +522,15 @@ class FileSystemContextMenu(QtWidgets.QMenu):
self.action_delete.triggered.connect(self._on_delete_triggered)
self.addAction(self.action_delete)
self.addSeparator()
action = self.addAction('Show in explorer')
system = platform.system()
if system == 'Windows':
text = 'Open in explorer'
elif system == 'Darwin':
text = 'Open in finder'
else:
text = 'Show in %s' % self.get_linux_file_explorer().capitalize()
action = self.addAction(text)
action.triggered.connect(self._on_show_in_explorer_triggered)
def get_new_user_actions(self):
@ -564,13 +575,20 @@ class FileSystemContextMenu(QtWidgets.QMenu):
def _on_create_file_triggered(self):
self.tree_view.helper.create_file()
def get_linux_file_explorer(self):
output = subprocess.check_output(
['xdg-mime', 'query', 'default', 'inode/directory']).decode()
if output:
explorer = output.splitlines()[0].replace(
'.desktop', '').split('.')[-1].lower()
return explorer
return 'nautilus'
def _on_show_in_explorer_triggered(self):
path = self.tree_view.helper.get_current_path()
system = platform.system()
if system == 'Linux':
output = subprocess.check_output(
['xdg-mime', 'query', 'default', 'inode/directory']).decode()
explorer = output.splitlines()[0].replace('.desktop', '')
explorer = self.get_linux_file_explorer()
if explorer in ['nautilus', 'dolphin']:
subprocess.Popen([explorer, '--select', path])
else:

View File

@ -45,16 +45,13 @@ class InteractiveConsole(QTextEdit):
self._app_msg_col = QColor("#4040FF")
self._stdin_col = QColor("#22AA22")
self._stderr_col = QColor("#FF0000")
self._process = None
self._write_app_messages = True
self._process_name = ''
self.process = None
self._args = None
self._usr_buffer = ""
self._clear_on_start = True
self.process = QProcess()
self._merge_outputs = False
self.process.finished.connect(self._on_process_finished)
self.process.error.connect(self._write_error)
self.process.readyReadStandardError.connect(self._on_stderr)
self.process.readyReadStandardOutput.connect(self._on_stdout)
self._running = False
self._writer = self.write
self._user_stop = False
@ -95,6 +92,14 @@ class InteractiveConsole(QTextEdit):
_logger().debug('%s', txt)
self._writer(self, txt, self.stderr_color)
@property
def write_app_messages(self):
return self._write_app_messages
@write_app_messages.setter
def write_app_messages(self, value):
self._write_app_messages = value
@property
def background_color(self):
""" The console background color. Default is white. """
@ -220,6 +225,11 @@ class InteractiveConsole(QTextEdit):
if args is None:
args = []
if not self._running:
self.process = QProcess()
self.process.finished.connect(self._on_process_finished)
self.process.error.connect(self._write_error)
self.process.readyReadStandardError.connect(self._on_stderr)
self.process.readyReadStandardOutput.connect(self._on_stdout)
if cwd:
self.process.setWorkingDirectory(cwd)
e = self.process.systemEnvironment()
@ -231,13 +241,13 @@ class InteractiveConsole(QTextEdit):
ev.insert(k, v)
self.process.setProcessEnvironment(ev)
self._running = True
self._process = process
self._process_name = process
self._args = args
if self._clear_on_start:
self.clear()
self._user_stop = False
self.process.start(process, args)
self._write_started()
self.process.start(process, args)
else:
_logger().warning('a process is already running')
@ -246,9 +256,11 @@ class InteractiveConsole(QTextEdit):
Stop the process (by killing it).
"""
_logger().debug('killing process')
self._user_stop = True
self.process.kill()
self.setReadOnly(True)
if self.process is not None:
self._user_stop = True
self.process.kill()
self.setReadOnly(True)
self._running = False
def keyPressEvent(self, event):
if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
@ -274,9 +286,10 @@ class InteractiveConsole(QTextEdit):
def _on_process_finished(self, exit_code, exit_status):
if not self._user_stop:
self._writer(
self, "\nProcess finished with exit code %d" %
exit_code, self._app_msg_col)
if self._write_app_messages:
self._writer(
self, "\nProcess finished with exit code %d" %
exit_code, self._app_msg_col)
self._running = False
_logger().debug('process finished (exit_code=%r, exit_status=%r)',
exit_code, exit_status)
@ -284,8 +297,10 @@ class InteractiveConsole(QTextEdit):
self.setReadOnly(True)
def _write_started(self):
if not self._write_app_messages:
return
self._writer(self, "{0} {1}\n".format(
self._process, " ".join(self._args)), self._app_msg_col)
self._process_name, " ".join(self._args)), self._app_msg_col)
self._running = True
_logger().debug('process started')
@ -295,7 +310,7 @@ class InteractiveConsole(QTextEdit):
self.app_msg_color)
else:
self._writer(self, "Failed to start {0} {1}\n".format(
self._process, " ".join(self._args)), self.app_msg_color)
self._process_name, " ".join(self._args)), self.app_msg_color)
err = PROCESS_ERROR_STRING[error]
self._writer(self, "Error: %s" % err, self.stderr_color)
_logger().debug('process error: %s', err)

View File

@ -43,8 +43,23 @@ class RecentFilesManager(QtCore.QObject):
:param default: default value.
:return: value
"""
return [os.path.normpath(pth) for pth in
self._settings.value('recent_files/%s' % key, default)]
def unique(seq, idfun=None):
if idfun is None:
def idfun(x):
return x
# order preserving
seen = {}
result = []
for item in seq:
marker = idfun(item)
if marker in seen:
continue
seen[marker] = 1
result.append(item)
return result
return unique([os.path.normpath(pth) for pth in
self._settings.value('recent_files/%s' % key, default)])
def set_value(self, key, value):
"""
@ -105,7 +120,7 @@ class MenuRecentFiles(QtWidgets.QMenu):
"""
Menu that manage the list of recent files.
To use the menu, simply pass connect to the open_requested signal.
To use the menu, simply connect to the open_requested signal.
"""
#: Signal emitted when the user clicked on a recent file action.

View File

@ -0,0 +1,172 @@
"""
This document contains the tree widget used to display the editor document
outline.
"""
from pyqode.core.panels import FoldingPanel
from pyqode.core.modes.outline import OutlineMode
from pyqode.qt import QtCore, QtGui, QtWidgets
from pyqode.core.api import TextBlockHelper, TextBlockUserData, TextHelper
class OutlineTreeWidget(QtWidgets.QTreeWidget):
"""
Displays the outline of a CodeEdit.
To use this widget:
1. add an OutlineMode to CodeEdit
2. call set_editor with a CodeEdit instance to show it's outline.
"""
def __init__(self, parent=None):
super(OutlineTreeWidget, self).__init__(parent)
self._editor = None
self._outline_mode = None
self._folding_panel = None
self._expanded_items = []
self.setHeaderHidden(True)
self.itemClicked.connect(self._on_item_clicked)
self.itemCollapsed.connect(self._on_item_state_changed)
self.itemExpanded.connect(self._on_item_state_changed)
self._updating = True
def set_editor(self, editor):
"""
Sets the current editor. The widget display the structure of that
editor.
:param editor: CodeEdit
"""
if self._outline_mode:
try:
self._outline_mode.document_changed.disconnect(self._on_changed)
except (TypeError, RuntimeError):
pass
if self._folding_panel:
try:
self._folding_panel.trigger_state_changed.disconnect(
self._on_block_state_changed)
except (TypeError, RuntimeError):
pass
self._editor = editor
if self._editor is not None:
try:
self._folding_panel = editor.panels.get(FoldingPanel)
except KeyError:
pass
else:
self._folding_panel.trigger_state_changed.connect(
self._on_block_state_changed)
try:
analyser = editor.modes.get(OutlineMode)
except KeyError:
self._outline_mode = None
else:
self._outline_mode = analyser
analyser.document_changed.connect(self._on_changed)
self._on_changed()
def _on_item_state_changed(self, item):
if self._updating:
return
block = item.data(0, QtCore.Qt.UserRole).block
assert isinstance(item, QtWidgets.QTreeWidgetItem)
item_state = not item.isExpanded()
block_state = TextBlockHelper.get_fold_trigger_state(block)
if item_state != block_state:
self._updating = True
self._folding_panel.toggle_fold_trigger(block)
self._updating = False
def _on_block_state_changed(self, block, state):
if self._updating:
return
data = block.userData()
if data is not None:
try:
item_state = not data.tree_item.isExpanded()
if item_state != state:
if state:
self.collapseItem(data.tree_item)
else:
self.expandItem(data.tree_item)
except AttributeError:
# a block that is not represented in the tree view has
# folded/unfolded, just ignore it
pass
def _on_changed(self):
"""
Update the tree items
"""
self._updating = True
to_collapse = []
self.clear()
if self._editor and self._outline_mode and self._folding_panel:
items, to_collapse = self.to_tree_widget_items(
self._outline_mode.definitions, to_collapse=to_collapse)
if len(items):
self.addTopLevelItems(items)
self.expandAll()
for item in reversed(to_collapse):
self.collapseItem(item)
self._updating = False
return
# no data
root = QtWidgets.QTreeWidgetItem()
root.setText(0, 'No data')
root.setIcon(0, QtGui.QIcon.fromTheme(
'dialog-information',
QtGui.QIcon(':/pyqode-icons/rc/dialog-info.png')))
self.addTopLevelItem(root)
self._updating = False
def _on_item_clicked(self, item):
"""
Go to the item position in the editor.
"""
if item:
name = item.data(0, QtCore.Qt.UserRole)
if name:
go = name.block.blockNumber()
helper = TextHelper(self._editor)
if helper.current_line_nbr() != go:
helper.goto_line(go, column=name.column)
self._editor.setFocus()
def to_tree_widget_items(self, definitions, to_collapse=None):
"""
Converts the list of top level definitions to a list of top level
tree items.
"""
def convert(name, editor, to_collapse):
ti = QtWidgets.QTreeWidgetItem()
ti.setText(0, name.name)
ti.setIcon(0, QtGui.QIcon(name.icon))
name.block = editor.document().findBlockByNumber(name.line)
ti.setData(0, QtCore.Qt.UserRole, name)
block_data = name.block.userData()
if block_data is None:
block_data = TextBlockUserData()
name.block.setUserData(block_data)
block_data.tree_item = ti
if to_collapse is not None and \
TextBlockHelper.get_fold_trigger_state(name.block):
to_collapse.append(ti)
for ch in name.children:
ti_ch, to_collapse = convert(ch, editor, to_collapse)
if ti_ch:
ti.addChild(ti_ch)
return ti, to_collapse
items = []
for d in definitions:
value, to_collapse = convert(d, self._editor, to_collapse)
items.append(value)
if to_collapse is not None:
return items, to_collapse
return items

View File

@ -4,9 +4,14 @@ This module contains the PromptLineEdit widget implementation.
"""
import os
from pyqode.qt import QtWidgets, QtCore, QtGui
from pyqode.qt import QT_API
from pyqode.qt import PYQT5_API
from pyqode.qt import PYQT4_API
from pyqode.qt import PYSIDE_API
class PromptLineEdit(QtWidgets.QLineEdit):
"""
Extends QLineEdit to show a prompt text and a clear icon
"""
@ -52,16 +57,20 @@ class PromptLineEdit(QtWidgets.QLineEdit):
def paintEvent(self, event):
super(PromptLineEdit, self).paintEvent(event)
qt_api = os.environ['QT_API'].lower()
if self._prompt_text and not self.text() and self.isEnabled():
if os.environ['QT_API'].lower() == 'pyqt4':
if qt_api in PYQT4_API:
from PyQt4.QtGui import QStyleOptionFrameV3
option = QStyleOptionFrameV3()
elif os.environ['QT_API'].lower() == 'pyside':
elif qt_api in PYSIDE_API:
from PySide.QtGui import QStyleOptionFrameV3
option = QStyleOptionFrameV3()
else:
elif qt_api in PYQT5_API:
from PyQt5.QtWidgets import QStyleOptionFrame
option = QStyleOptionFrame()
else:
msg = 'Qt bindings "%s" is not supported' % qt_api
raise PythonQtError(msg)
self.initStyleOption(option)

View File

@ -1,6 +1,7 @@
"""
This module contains the splittable tab widget API
"""
import inspect
import logging
import mimetypes
import os
@ -770,8 +771,13 @@ class SplittableCodeEditTabWidget(SplittableTabWidget):
def register_code_edit(cls, code_edit_class):
"""
Register an additional code edit **class**
:param code_edit_class: code edit class to regiter.
.. warning: This method expect a class, not an instance!
:param code_edit_class: code edit class to register.
"""
if not inspect.isclass(code_edit_class):
raise TypeError('must be a class, not an instance.')
for mimetype in code_edit_class.mimetypes:
if mimetype in cls.editors:
_logger().warn('editor for mimetype already registered, '
@ -814,27 +820,37 @@ class SplittableCodeEditTabWidget(SplittableTabWidget):
open/create.
:type mimetype: mime type
:return: CodeEdit instance
:param args: Positional arguments that must be forwarded to the editor
widget constructor.
:param kwargs: Keyworded arguments that must be forwarded to the editor
widget constructor.
:return: Code editor widget instance.
"""
if mimetype in self.editors.keys():
return self.editors[mimetype](
*args, parent=self.main_tab_widget, **kwargs)
return self.fallback_editor(parent=self.main_tab_widget)
return self.fallback_editor(*args, parent=self.main_tab_widget,
**kwargs)
def create_new_document(self, base_name='New Document', extension='.txt'):
def create_new_document(self, base_name='New Document', extension='.txt',
*args, **kwargs):
"""
Creates a new document.
The document name will be ``base_name + count + extension``
:type base_name: Base name of the document. An int will be appended.
:type extension: Document extension (must include the DOT)
:param base_name: Base name of the document. An int will be appended.
:param extension: Document extension (dotted)
:param args: Positional arguments that must be forwarded to the editor
widget constructor.
:param kwargs: Keyworded arguments that must be forwarded to the editor
widget constructor.
:return: The created code editor
:return: Code editor widget instance.
"""
SplittableCodeEditTabWidget._new_count += 1
name = '%s%d%s' % (base_name, self._new_count, extension)
tab = self._create_code_edit(self.guess_mimetype(name))
tab = self._create_code_edit(self.guess_mimetype(name), *args, **kwargs)
tab.setDocumentTitle(name)
self.add_tab(tab, title=name, icon=self._icon(name))
return tab

View File

@ -6,5 +6,5 @@ panels for the frontend and a series of worker for the backend (code
completion, documentation lookups, code linters, and so on...).
"""
__version__ = '2.4.dev'
__version__ = '2.5.0'

View File

@ -35,7 +35,7 @@ if __name__ == '__main__':
# add user paths to sys.path
if args.syspath:
for path in args.syspath:
print('append path %s to sys.path\n' % path)
print('append path %s to sys.path' % path)
sys.path.append(path)
from pyqode.core import backend

View File

@ -7,6 +7,7 @@ import logging
import os
import tempfile
import jedi
from pyqode.core.share import Definition
def _logger():
@ -73,66 +74,6 @@ def goto_assignments(request_data):
_old_definitions = {}
class Definition(object):
"""
Represents a defined name in a python source code (import, function, class,
method). Definition usually form a tree limited to 2 levels (we stop at the
method level).
"""
def __init__(self, name='', icon='', line=1, column=0, full_name=''):
#: Icon resource name associated with the definition, can be None
self.icon = icon
#: Definition name (name of the class, method, variable)
self.name = name
#: The line of the definition in the current editor text
self.line = line
#: The column of the definition in the current editor text
self.column = column
#: Symbol name + parent name (for methods and class variables)
self.full_name = full_name
#: Possible list of children (only classes have children)
self.children = []
if self.full_name == "":
self.full_name = self.name
def add_child(self, definition):
"""
Adds a child definition
"""
self.children.append(definition)
def to_dict(self):
"""
Serialises a definition to a dictionary, ready for json.
Children are serialised recursively.
"""
ddict = {'name': self.name, 'icon': self.icon,
'line': self.line, 'column': self.column,
'full_name': self.full_name, 'children': []}
for child in self.children:
ddict['children'].append(child.to_dict())
return ddict
def from_dict(self, ddict):
"""
Deserialise the definition from a simple dict.
"""
self.name = ddict['name']
self.icon = ddict['icon']
self.line = ddict['line']
self.column = ddict['column']
self.full_name = ddict['full_name']
self.children[:] = []
for child_dict in ddict['children']:
self.children.append(Definition().from_dict(child_dict))
return self
def __repr__(self):
return 'Definition(%r, %r, %r, %r)' % (self.name, self.icon,
self.line, self.column)
def _extract_def(d):
d_line, d_column = d.start_pos
# use full name for import type
@ -144,8 +85,8 @@ def _extract_def(d):
name = d.name
else:
name = d.name
definition = Definition(name, icon_from_typename(d.name, d.type),
d_line - 1, d_column, d.full_name)
definition = Definition(name, d_line - 1, d_column,
icon_from_typename(d.name, d.type))
# check for methods in class or nested methods/classes
if d.type == "class" or d.type == 'function':
try:
@ -210,7 +151,7 @@ def run_pep8(request_data):
path = request_data['path']
# setup our custom style guide with our custom checker which returns a list
# of strings instread of spitting the results at stdout
pep8style = pep8.StyleGuide(parse_argv=False, config_file=True,
pep8style = pep8.StyleGuide(parse_argv=False, config_file='',
checker_class=CustomChecker)
try:
results = pep8style.input_file(path, lines=code.splitlines(True))

View File

@ -8,7 +8,6 @@ from .autocomplete import PyAutoCompleteMode
from .autoindent import PyAutoIndentMode
from .calltips import CalltipsMode
from .comments import CommentsMode
from .document_analyser import DocumentAnalyserMode
from .frosted_checker import FrostedCheckerMode
from .goto_assignements import Assignment
from .goto_assignements import GoToAssignmentsMode
@ -30,7 +29,6 @@ __all__ = [
'Assignment',
'CalltipsMode',
'CommentsMode',
'DocumentAnalyserMode',
'FrostedCheckerMode',
'GoToAssignmentsMode',
'PEP8CheckerMode',

View File

@ -237,7 +237,10 @@ class PyAutoIndentMode(AutoIndentMode):
tc2 = QTextCursor(tc)
tc2.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor)
char = tc2.selectedText()
return char
while char == ' ':
tc2.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor)
char = tc2.selectedText()
return char.strip()
def _handle_indent_between_paren(self, column, line, parent_impl, tc):
"""
@ -254,7 +257,7 @@ class PyAutoIndentMode(AutoIndentMode):
open_line_indent = len(open_line_txt) - len(open_line_txt.lstrip())
if prev_open:
post = (open_line_indent + self.editor.tab_length) * ' '
elif next_close:
elif next_close and prev_char != ',':
post = open_line_indent * ' '
elif tc.block().blockNumber() == open_line:
post = open_symbol_col * ' '

View File

@ -1,127 +0,0 @@
# -*- coding: utf-8 -*-
import logging
from pyqode.core import api
from pyqode.core.api import Mode, TextBlockUserData, TextBlockHelper
from pyqode.core.api import DelayJobRunner
from pyqode.core.backend import NotRunning
from pyqode.python.backend.workers import Definition, defined_names
from pyqode.qt import QtCore, QtGui, QtWidgets
def _logger():
return logging.getLogger(__name__)
class DocumentAnalyserMode(Mode, QtCore.QObject):
""" Analyses the document outline as a tree of statements.
This mode analyses the structure of a document (a tree of
:class:`pyqode.python.backend.workers.Definition`.
:attr:`pyqode.python.modes.DocumentAnalyserMode.document_changed`
is emitted whenever the document structure changed.
To keep good performances, the analysis task is run when the application is
idle for more than 1 second (by default).
"""
#: Signal emitted when the document structure changed.
document_changed = QtCore.Signal()
def __init__(self, delay=1000):
Mode.__init__(self)
QtCore.QObject.__init__(self)
self._jobRunner = DelayJobRunner(delay=delay)
#: The list of results (elements might have children; this is actually
#: a tree).
self.results = []
def on_state_changed(self, state):
if state:
self.editor.new_text_set.connect(self._run_analysis)
self.editor.textChanged.connect(self._request_analysis)
else:
self.editor.textChanged.disconnect(self._request_analysis)
self.editor.new_text_set.disconnect(self._run_analysis)
self._jobRunner.cancel_requests()
def _request_analysis(self):
self._jobRunner.request_job(self._run_analysis)
def _run_analysis(self):
if self.enabled and self.editor and self.editor.toPlainText() and \
self.editor.file:
request_data = {
'code': self.editor.toPlainText(),
'path': self.editor.file.path,
'encoding': self.editor.file.encoding
}
try:
self.editor.backend.send_request(
defined_names, request_data,
on_receive=self._on_results_available)
except NotRunning:
QtCore.QTimer.singleShot(100, self._run_analysis)
else:
self.results = []
self.document_changed.emit()
def _on_results_available(self, results):
if results:
results = [Definition().from_dict(ddict) for ddict in results]
self.results = results
if self.results is not None:
_logger().debug("Document structure changed")
self.document_changed.emit()
@property
def flattened_results(self):
"""
Flattens the document structure tree as a simple sequential list.
"""
ret_val = []
for d in self.results:
ret_val.append(d)
for sub_d in d.children:
nd = Definition(sub_d.name, sub_d.icon, sub_d.line,
sub_d.column, sub_d.full_name)
nd.name = " " + nd.name
nd.full_name = " " + nd.full_name
ret_val.append(nd)
return ret_val
def to_tree_widget_items(self, to_collapse=None):
"""
Returns the results as a list of top level QTreeWidgetItem.
This is a convenience function that you can use to update a document
tree widget wheneve the document changed.
"""
def convert(name, editor, to_collapse):
ti = QtWidgets.QTreeWidgetItem()
ti.setText(0, name.name)
ti.setIcon(0, QtGui.QIcon(name.icon))
name.block = editor.document().findBlockByNumber(name.line)
ti.setData(0, QtCore.Qt.UserRole, name)
block_data = name.block.userData()
if block_data is None:
block_data = TextBlockUserData()
name.block.setUserData(block_data)
block_data.tree_item = ti
if to_collapse is not None and \
TextBlockHelper.get_fold_trigger_state(name.block):
to_collapse.append(ti)
for ch in name.children:
ti_ch, to_collapse = convert(ch, editor, to_collapse)
if ti_ch:
ti.addChild(ti_ch)
return ti, to_collapse
items = []
for d in self.results:
value, to_collapse = convert(d, self.editor, to_collapse)
items.append(value)
if to_collapse is not None:
return items, to_collapse
return items

View File

@ -5,6 +5,7 @@ SymbolBrowserPanel
import logging
from pyqode.core.api import Panel, TextHelper
from pyqode.qt import QtGui, QtCore, QtWidgets
from pyqode.core.share import Definition
def _logger():
@ -39,26 +40,40 @@ class SymbolBrowserPanel(Panel):
self._on_cursor_pos_changed)
try:
self.editor.modes.get(
'DocumentAnalyserMode').document_changed.connect(
'OutlineMode').document_changed.connect(
self._on_document_changed)
except KeyError:
_logger().warning("No DocumentAnalyserMode found, install it "
_logger().warning("No OutlineMode found, install it "
"before SymbolBrowserPanel!")
else:
self.editor.cursorPositionChanged.disconnect(
self._on_cursor_pos_changed)
try:
self.editor.modes.get(
'DocumentAnalyserMode').document_changed.disconnect(
'OutlineMode').document_changed.disconnect(
self._on_document_changed)
except KeyError:
pass
def _on_document_changed(self):
def flatten(results):
"""
Flattens the document structure tree as a simple sequential list.
"""
ret_val = []
for de in results:
ret_val.append(de)
for sub_d in de.children:
nd = Definition(
sub_d.name, sub_d.line, sub_d.column, sub_d.icon)
nd.name = " " + nd.name
ret_val.append(nd)
return ret_val
if not self or not self.editor:
return
mode = self.editor.modes.get('DocumentAnalyserMode')
definitions = mode.flattened_results
mode = self.editor.modes.get('OutlineMode')
definitions = flatten(mode.definitions)
self.combo_box.clear()
if definitions:
self.combo_box.addItem(" < Select a symbol >")

View File

@ -7,6 +7,7 @@ This packages contains the available python widgets:
"""
from .code_edit import PyCodeEditBase, PyCodeEdit
from .interactive import PyInteractiveConsole
# todo PyOutlineTreeWidget is deprecated and should be removed soon.
from .outline import PyOutlineTreeWidget

View File

@ -12,6 +12,7 @@ from pyqode.core import panels
from pyqode.python import managers as pymanagers
from pyqode.python import modes as pymodes
from pyqode.python import panels as pypanels
from pyqode.python.backend.workers import defined_names
from pyqode.python.folding import PythonFoldDetector
@ -26,8 +27,7 @@ class PyCodeEditBase(api.CodeEdit):
encoding.
"""
def __init__(self, parent=None, create_default_actions=True,
color_scheme='qt'):
def __init__(self, parent=None, create_default_actions=True):
super(PyCodeEditBase, self).__init__(parent, create_default_actions)
self.file = pymanagers.PyFileManager(self)
@ -53,16 +53,17 @@ class PyCodeEdit(PyCodeEditBase):
def __init__(self, parent=None, server_script=server.__file__,
interpreter=sys.executable, args=None,
create_default_actions=True, color_scheme='qt'):
create_default_actions=True, color_scheme='qt',
reuse_backend=False):
super(PyCodeEdit, self).__init__(
parent=parent, create_default_actions=create_default_actions,
color_scheme=color_scheme)
self.backend.start(server_script, interpreter, args)
parent=parent, create_default_actions=create_default_actions)
self.backend.start(server_script, interpreter, args,
reuse=reuse_backend)
self.setLineWrapMode(self.NoWrap)
self.setWindowTitle("pyQode - Python Editor")
# install those modes first as they are required by other modes/panels
self.modes.append(pymodes.DocumentAnalyserMode())
self.modes.append(modes.OutlineMode(defined_names))
# panels
self.panels.append(panels.FoldingPanel())
@ -87,6 +88,7 @@ class PyCodeEdit(PyCodeEditBase):
self.modes.append(modes.OccurrencesHighlighterMode())
self.modes.append(modes.SmartBackSpaceMode())
self.modes.append(modes.ExtendedSelectionMode())
self.modes.append(modes.CaseConverterMode())
# python specifics
self.modes.append(pymodes.PyAutoIndentMode())
self.modes.append(pymodes.PyAutoCompleteMode())

Some files were not shown because too many files have changed in this diff Show More