1
0
mirror of https://github.com/gryf/.vim.git synced 2025-12-17 11:30:29 +01:00

Update for plugins: pyflakes and pydoc. Added awesome plugin for autocompletion acp.

This commit is contained in:
2010-11-25 21:53:22 +01:00
parent 0899a3d3ca
commit 0a1e903e9e
19 changed files with 2607 additions and 420 deletions

View File

@@ -85,16 +85,15 @@ function! ShowPyDoc(name, type)
endif
endfunction
"highlighting
function! Highlight(name)
execute "sb __doc__"
set filetype=man
syn on
execute 'syntax keyword pydoc '.s:name2
"syn on
execute 'syntax keyword pydoc '.a:name
hi pydoc gui=reverse
endfunction
"mappings
au FileType python,man map <buffer> <leader>pw :call ShowPyDoc('<C-R><C-W>', 1)<CR>
au FileType python,man map <buffer> <leader>pW :call ShowPyDoc('<C-R><C-A>', 1)<CR>

View File

@@ -46,14 +46,20 @@ if sys.version_info[:2] < (2, 5):
scriptdir = os.path.join(os.path.dirname(vim.eval('expand("<sfile>")')), 'pyflakes')
sys.path.insert(0, scriptdir)
from pyflakes import checker, ast, messages
import ast
from pyflakes import checker, messages
from operator import attrgetter
import re
class loc(object):
def __init__(self, lineno, col=None):
self.lineno = lineno
self.col_offset = col
class SyntaxError(messages.Message):
message = 'could not compile: %s'
def __init__(self, filename, lineno, col, message):
messages.Message.__init__(self, filename, lineno, col)
messages.Message.__init__(self, filename, loc(lineno, col))
self.message_args = (message,)
class blackhole(object):
@@ -67,13 +73,12 @@ def check(buffer):
# assume everything else that follows is encoded in the encoding.
encoding_found = False
for n, line in enumerate(contents):
if not encoding_found:
if re.match(r'^# -\*- coding: .+? -*-', line):
encoding_found = True
else:
# skip all preceeding lines
contents = [''] * n + contents[n:]
if n >= 2:
break
elif re.match(r'#.*coding[:=]\s*([-\w.]+)', line):
contents = ['']*(n+1) + contents[n+1:]
break
contents = '\n'.join(contents) + '\n'
vimenc = vim.eval('&encoding')
@@ -82,7 +87,7 @@ def check(buffer):
builtins = []
try:
builtins = eval(vim.eval('string(g:pyflakes_builtins)'))
builtins = set(eval(vim.eval('string(g:pyflakes_builtins)')))
except Exception:
pass
@@ -90,7 +95,7 @@ def check(buffer):
# TODO: use warnings filters instead of ignoring stderr
old_stderr, sys.stderr = sys.stderr, blackhole()
try:
tree = ast.parse(contents, filename)
tree = ast.parse(contents, filename or '<unknown>')
finally:
sys.stderr = old_stderr
except:
@@ -104,7 +109,15 @@ def check(buffer):
return [SyntaxError(filename, lineno, offset, str(value))]
else:
w = checker.Checker(tree, filename, builtins = builtins)
# pyflakes looks to _MAGIC_GLOBALS in checker.py to see which
# UndefinedNames to ignore
old_globals = getattr(checker,' _MAGIC_GLOBALS', [])
checker._MAGIC_GLOBALS = set(old_globals) | builtins
w = checker.Checker(tree, filename)
checker._MAGIC_GLOBALS = old_globals
w.messages.sort(key = attrgetter('lineno'))
return w.messages
@@ -233,7 +246,7 @@ for w in check(vim.current.buffer):
vim.command("let l:qf_item.text = '%s'" % vim_quote(w.message % w.message_args))
vim.command("let l:qf_item.type = 'E'")
if w.col is None or isinstance(w, SyntaxError):
if getattr(w, 'col', None) is None or isinstance(w, SyntaxError):
# without column information, just highlight the whole line
# (minus the newline)
vim.command(r"let s:mID = matchadd('PyFlakes', '\%" + str(w.lineno) + r"l\n\@!')")

View File

@@ -0,0 +1,29 @@
0.4.0 (2009-11-25):
- Fix reporting for certain SyntaxErrors which lack line number
information.
- Check for syntax errors more rigorously.
- Support checking names used with the class decorator syntax in versions
of Python which have it.
- Detect local variables which are bound but never used.
- Handle permission errors when trying to read source files.
- Handle problems with the encoding of source files.
- Support importing dotted names so as not to incorrectly report them as
redefined unused names.
- Support all forms of the with statement.
- Consider static `__all__` definitions and avoid reporting unused names
if the names are listed there.
- Fix incorrect checking of class names with respect to the names of their
bases in the class statement.
- Support the `__path__` global in `__init__.py`.
0.3.0 (2009-01-30):
- Display more informative SyntaxError messages.
- Don't hang flymake with unmatched triple quotes (only report a single
line of source for a multiline syntax error).
- Recognize __builtins__ as a defined name.
- Improve pyflakes support for python versions 2.3-2.5
- Support for if-else expressions and with statements.
- Warn instead of error on non-existant file paths.
- Check for __future__ imports after other statements.
- Add reporting for some types of import shadowing.
- Improve reporting of unbound locals

View File

@@ -1,36 +1,80 @@
pyflakes
========
pyflakes-vim
============
This version of PyFlakes_ has been improved to use Python's newer ``ast``
module, instead of ``compiler``. So code checking happens faster, and will stay
up to date with new language changes.
A Vim plugin for checking Python code on the fly.
.. _PyFlakes: http://www.divmod.org/trac/wiki/DivmodPyflakes
PyFlakes catches common Python errors like mistyping a variable name or
accessing a local before it is bound, and also gives warnings for things like
unused imports.
pyflakes-vim uses the output from PyFlakes to highlight errors in your code.
To locate errors quickly, use quickfix_ commands like :cc.
Make sure to check vim.org_ for the latest updates.
.. _pyflakes.vim: http://www.vim.org/scripts/script.php?script_id=2441
.. _vim.org: http://www.vim.org/scripts/script.php?script_id=2441
.. _quickfix: http://vimdoc.sourceforge.net/htmldoc/quickfix.html#quickfix
Quick Installation
------------------
1. Make sure your ``.vimrc`` has::
filetype on " enables filetype detection
filetype plugin on " enables filetype specific plugins
2. Download the latest release_.
3. Unzip ``pyflakes.vim`` and the ``pyflakes`` directory into
``~/.vim/ftplugin/python`` (or somewhere similar on your
`runtime path`_ that will be sourced for Python files).
.. _release: http://www.vim.org/scripts/script.php?script_id=2441
.. _runtime path: http://vimdoc.sourceforge.net/htmldoc/options.html#'runtimepath'
Installation
------------
If you downloaded this from vim.org_, then just drop the contents of the zip
file into ``~/.vim/ftplugin/python``.
Otherwise, if you're running "from source," you'll need PyFlakes on your
PYTHONPATH somewhere. I recommend getting my PyFlakes_ fork, which retains
column number information and has therfore has more specific error locations.
.. _vim.org: http://www.vim.org/scripts/script.php?script_id=2441
.. _PyFlakes: http://github.com/kevinw/pyflakes
Hacking
-------
::
git clone git://github.com/kevinw/pyflakes-vim.git
cd pyflakes-vim
git clone git://github.com/kevinw/pyflakes.git
Options
-------
Set this option to you vimrc file to disable quickfix support::
let g:pyflakes_use_quickfix = 0
The value is set to 1 by default.
TODO
----
* signs_ support (show warning and error icons to left of the buffer area)
* configuration variables
* parse or intercept useful output from the warnings module
Importing several modules from the same package results in unnecessary warnings:
.. _signs: http://www.vim.org/htmldoc/sign.html
::
Changelog
---------
import a.b
import a.c # Redefinition of unused "a" from line 1
The following construct for defining a function differently depending on some
condition results in a redefinition warning:
::
if some_condition:
def foo(): do_foo()
else:
def foo(): do_bar() # redefinition of function 'foo' from line 2
IDE Integration
---------------
* vim: pyflakes-vim_
.. _pyflakes-vim: http://github.com/kevinw/pyflakes-vim
Please see http://www.vim.org/scripts/script.php?script_id=2441 for a history of
all changes.

View File

@@ -1,11 +0,0 @@
- Check for methods that override other methods except that they vary by case.
- assign/increment + unbound local error not caught
def foo():
bar = 5
def meep():
bar += 2
meep()
print bar
print foo()

View File

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

View File

@@ -1,54 +1,147 @@
import ast
from pyflakes import messages
# -*- test-case-name: pyflakes -*-
# (c) 2005-2010 Divmod, Inc.
# See LICENSE file for details
import __builtin__
import os.path
import _ast
from pyflakes import messages
allowed_before_future = (ast.Module, ast.ImportFrom, ast.Expr, ast.Str)
defined_names = set(('__file__', '__builtins__'))
# utility function to iterate over an AST node's children, adapted
# from Python 2.6's standard ast module
try:
import ast
iter_child_nodes = ast.iter_child_nodes
except (ImportError, AttributeError):
def iter_child_nodes(node, astcls=_ast.AST):
"""
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 node._fields:
field = getattr(node, name, None)
if isinstance(field, astcls):
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))
class UnBinding(Binding):
'''Created by the 'del' operator.'''
class Importation(Binding):
"""
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
name = name.split('.')[0]
super(Importation, self).__init__(name, source)
class Argument(Binding):
"""
Represents binding a name as an argument.
"""
class Assignment(Binding):
pass
"""
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(Binding):
_property_decorator = False
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 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):
import_starred = False # set to True when import * is found
importStarred = False # set to True when import * is found
def __repr__(self):
return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
def __init__(self):
super(Scope, self).__init__()
class ClassScope(Scope):
pass
@@ -69,340 +162,464 @@ class FunctionScope(Scope):
class ModuleScope(Scope):
pass
class Checker(ast.NodeVisitor):
def __init__(self, tree, filename='(none)', builtins = None):
ast.NodeVisitor.__init__(self)
self.deferred = []
# Globally defined names which are not attributes of the __builtin__ module.
_MAGIC_GLOBALS = ['__file__', '__builtins__']
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
traceTree = False
def __init__(self, tree, filename='(none)'):
self._deferredFunctions = []
self._deferredAssignments = []
self.dead_scopes = []
self.messages = []
self.filename = filename
self.scope_stack = [ModuleScope()]
self.futures_allowed = True
self.builtins = frozenset(builtins or [])
self.visit(tree)
for handler, scope in self.deferred:
self.scope_stack = scope
handler()
del self.scope_stack[1:]
self.pop_scope()
self.scopeStack = [ModuleScope()]
self.futuresAllowed = True
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
# noisly if called after we've run through the deferred assignments.
self._deferredAssignments = None
del self.scopeStack[1:]
self.popScope()
self.check_dead_scopes()
def defer(self, callable):
'''Schedule something to be called after just before completion.
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.deferred.append( (callable, self.scope_stack[:]) )
self._deferredFunctions.append((callable, self.scopeStack[:]))
def deferAssignment(self, callable):
"""
Schedule an assignment handler to be called just after deferred
function handlers.
"""
self._deferredAssignments.append((callable, self.scopeStack[:]))
def _runDeferred(self, deferred):
"""
Run the callables in C{deferred} using their associated scope stack.
"""
for handler, scope in deferred:
self.scopeStack = scope
handler()
def scope(self):
return self.scopeStack[-1]
scope = property(scope)
def popScope(self):
self.dead_scopes.append(self.scopeStack.pop())
def check_dead_scopes(self):
# Check for modules that were imported but unused
"""
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()
if os.path.split(self.filename)[1] != '__init__.py':
# Look for possible mistakes in the export list
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.
for importation in scope.itervalues():
if isinstance(importation, Importation) and not importation.used:
self.report(messages.UnusedImport, importation.source.lineno, importation.name)
if isinstance(importation, Importation):
if not importation.used and importation.name not in all:
self.report(
messages.UnusedImport,
importation.source,
importation.name)
def push_function_scope(self):
self.scope_stack.append(FunctionScope())
def push_class_scope(self):
self.scope_stack.append(ClassScope())
def pushFunctionScope(self):
self.scopeStack.append(FunctionScope())
def pop_scope(self):
scope = self.scope_stack.pop()
self.dead_scopes.append(scope)
def pushClassScope(self):
self.scopeStack.append(ClassScope())
@property
def scope(self):
return self.scope_stack[-1]
def report(self, messageClass, *args, **kwargs):
self.messages.append(messageClass(self.filename, *args, **kwargs))
def report(self, message_class, *args, **kwargs):
self.messages.append(message_class(self.filename, *args, **kwargs))
def handleChildren(self, tree):
for node in iter_child_nodes(tree):
self.handleNode(node, tree)
def visit_Import(self, node):
for name_node in node.names:
# "import bar as foo" -> name=bar, asname=foo
name = name_node.asname or name_node.name
self.add_binding(node, Importation(name, node))
def visit_GeneratorExp(self, node):
for generator in node.generators:
self.visit(generator.iter)
self.assign_vars(generator.target)
for generator in node.generators:
if hasattr(node, 'elt'):
self.visit(node.elt)
self.visit_nodes(generator.ifs)
visit_ListComp = visit_GeneratorExp
def visit_For(self, node):
'''
Process bindings for loop variables.
'''
self.visit_nodes(node.iter)
for var in self.flatten(node.target):
upval = self.scope.get(var.id)
if isinstance(upval, Importation) and upval.used:
self.report(messages.ImportShadowedByLoopVar,
node.lineno, node.col_offset, var.id, upval.source.lineno)
self.add_binding(var, Assignment(var.id, var))
self.visit_nodes(node.body + node.orelse)
def visit_FunctionDef(self, node):
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 handleNode(self, node, parent):
node.parent = parent
if self.traceTree:
print ' ' * self.nodeDepth + node.__class__.__name__
self.nodeDepth += 1
if self.futuresAllowed and not \
(isinstance(node, _ast.ImportFrom) or self.isDocstring(node)):
self.futuresAllowed = False
nodeType = node.__class__.__name__.upper()
try:
decorators = node.decorator_list
except AttributeError:
# Use .decorators for Python 2.5 compatibility
decorators = node.decorators
handler = getattr(self, nodeType)
handler(node)
finally:
self.nodeDepth -= 1
if self.traceTree:
print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__
self.visit_nodes(decorators)
def ignore(self, node):
pass
# Check for property decorator
func_def = FunctionDefinition(node.name, node)
# "stmt" type nodes
RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \
TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren
for decorator in decorators:
if getattr(decorator, 'attr', None) in ('setter', 'deleter'):
func_def._property_decorator = True
CONTINUE = BREAK = PASS = ignore
self.add_binding(node, func_def)
# "expr" type nodes
BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \
CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren
self.visit_Lambda(node)
NUM = STR = ELLIPSIS = ignore
def visit_Lambda(self, node):
self.visit_nodes(node.args.defaults)
# "slice" type nodes
SLICE = EXTSLICE = INDEX = handleChildren
def run_function():
self.push_function_scope()
# expression contexts are node instances too, though being constants
LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore
# Check for duplicate arguments
argnames = set()
for arg in self.flatten(node.args.args):
if arg.id in argnames:
self.report(messages.DuplicateArgument, arg.lineno, arg.col_offset, arg.id)
argnames.add(arg.id)
# 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
self.assign_vars(node.args.args, report_redef=False)
if node.args.vararg is not None:
self.add_binding(node, Assignment(node.args.vararg, node), False)
if node.args.kwarg is not None:
self.add_binding(node, Assignment(node.args.kwarg, node), False)
self.visit_nodes(node.body)
self.pop_scope()
# additional node types
COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren
self.defer(run_function)
def addBinding(self, loc, value, reportRedef=True):
'''Called when a binding is altered.
def visit_Name(self, node):
- `loc` is the location (an object with lineno and optionally
col_offset attributes) of 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 `reportRedef` is True (default), rebinding while unused will be
reported.
'''
Locate names in locals / function / globals scopes.
'''
scope, name = self.scope, node.id
if (isinstance(self.scope.get(value.name), FunctionDefinition)
and isinstance(value, FunctionDefinition)):
self.report(messages.RedefinedFunction,
loc, value.name, self.scope[value.name].source)
# try local scope
import_starred = scope.import_starred
try:
scope[name].used = (scope, node.lineno, node.col_offset)
except KeyError:
pass
else:
return
if not isinstance(self.scope, ClassScope):
for scope in self.scopeStack[::-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 reportRedef):
# try enclosing function scopes
for func_scope in self.scope_stack[-2:0:-1]:
import_starred = import_starred or func_scope.import_starred
if not isinstance(func_scope, FunctionScope):
continue
self.report(messages.RedefinedWhileUnused,
loc, value.name, scope[value.name].source)
if isinstance(value, UnBinding):
try:
func_scope[name].used = (scope, node.lineno, node.col_offset)
del self.scope[value.name]
except KeyError:
self.report(messages.UndefinedName, loc, value.name)
else:
self.scope[value.name] = value
def GLOBAL(self, node):
"""
Keep track of globals declarations.
"""
if isinstance(self.scope, FunctionScope):
self.scope.globals.update(dict.fromkeys(node.names))
def LISTCOMP(self, node):
# handle generators before element
for gen in node.generators:
self.handleNode(gen, node)
self.handleNode(node.elt, node)
GENERATOREXP = SETCOMP = LISTCOMP
# dictionary comprehensions; introduced in Python 2.7
def DICTCOMP(self, node):
for gen in node.generators:
self.handleNode(gen, node)
self.handleNode(node.key, node)
self.handleNode(node.value, node)
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 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.handleChildren(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)):
# try local scope
importStarred = self.scope.importStarred
try:
self.scope[node.id].used = (self.scope, node)
except KeyError:
pass
else:
return
# try global scope
import_starred = import_starred or self.scope_stack[0].import_starred
try:
self.scope_stack[0][node.id].used = (scope, node.lineno, node.col_offset)
except KeyError:
if not import_starred and not self.is_builtin(name):
self.report(messages.UndefinedName, node.lineno, node.col_offset, name)
# try enclosing function scopes
def assign_vars(self, targets, report_redef=True):
scope = self.scope
for scope in self.scopeStack[-2:0:-1]:
importStarred = importStarred or scope.importStarred
if not isinstance(scope, FunctionScope):
continue
try:
scope[node.id].used = (self.scope, node)
except KeyError:
pass
else:
return
for target in self.flatten(targets):
name = target.id
# try global scope
importStarred = importStarred or self.scopeStack[0].importStarred
try:
self.scopeStack[0][node.id].used = (self.scope, node)
except KeyError:
if ((not hasattr(__builtin__, node.id))
and node.id not in _MAGIC_GLOBALS
and not importStarred):
if (os.path.basename(self.filename) == '__init__.py' and
node.id == '__path__'):
# the special name __path__ is valid only in packages
pass
else:
self.report(messages.UndefinedName, node, node.id)
elif isinstance(node.ctx, (_ast.Store, _ast.AugStore)):
# if the name hasn't already been defined in the current scope
if isinstance(scope, FunctionScope) and name not in scope:
if isinstance(self.scope, FunctionScope) and node.id not in self.scope:
# for each function or module scope above us
for upscope in self.scope_stack[:-1]:
if not isinstance(upscope, (FunctionScope, ModuleScope)):
for scope in self.scopeStack[:-1]:
if not isinstance(scope, (FunctionScope, ModuleScope)):
continue
upval = upscope.get(name)
# 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
if upval is not None:
if upval.used and upval.used[0] is scope and name not in scope.globals:
# then it's probably a mistake
self.report(messages.UndefinedLocal,
upval.used[1], upval.used[2], name, upval.source.lineno, upval.source.col_offset)
if (node.id in scope
and scope[node.id].used
and scope[node.id].used[0] is self.scope
and node.id not in self.scope.globals):
# then it's probably a mistake
self.report(messages.UndefinedLocal,
scope[node.id].used[1],
node.id,
scope[node.id].source)
break
self.add_binding(target, Assignment(name, target), report_redef)
def visit_Assign(self, node):
for target in node.targets:
self.visit_nodes(node.value)
self.assign_vars(node.targets)
def visit_Delete(self, node):
for target in self.flatten(node.targets):
if isinstance(self.scope, FunctionScope) and target.id in self.scope.globals:
del self.scope.globals[target.id]
if isinstance(node.parent,
(_ast.For, _ast.comprehension, _ast.Tuple, _ast.List)):
binding = Binding(node.id, node)
elif (node.id == '__all__' and
isinstance(self.scope, ModuleScope)):
binding = ExportBinding(node.id, node.parent.value)
else:
self.add_binding(target, UnBinding(target.id, target))
def visit_With(self, node):
self.visit(node.context_expr)
# handle new bindings made by optional "as" part
if node.optional_vars is not None:
self.assign_vars(node.optional_vars)
self.visit_nodes(node.body)
def visit_ImportFrom(self, node):
if node.module == '__future__':
if not self.futures_allowed:
self.report(messages.LateFutureImport, node.lineno, node.col_offset, [alias.name for alias in node.names])
binding = Assignment(node.id, node)
if node.id in self.scope:
binding.used = self.scope[node.id].used
self.addBinding(node, binding)
elif isinstance(node.ctx, _ast.Del):
if isinstance(self.scope, FunctionScope) and \
node.id in self.scope.globals:
del self.scope.globals[node.id]
else:
self.addBinding(node, UnBinding(node.id, node))
else:
self.futures_allowed = False
# 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 FUNCTIONDEF(self, node):
# the decorators attribute is called decorator_list as of Python 2.6
if hasattr(node, 'decorators'):
for deco in node.decorators:
self.handleNode(deco, node)
else:
for deco in node.decorator_list:
self.handleNode(deco, node)
self.addBinding(node, FunctionDefinition(node.name, node))
self.LAMBDA(node)
def LAMBDA(self, node):
for default in node.args.defaults:
self.handleNode(default, node)
def runFunction():
args = []
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)
self.pushFunctionScope()
addArgs(node.args.args)
# vararg/kwarg identifiers are not Name nodes
if node.args.vararg:
args.append(node.args.vararg)
if node.args.kwarg:
args.append(node.args.kwarg)
for name in args:
self.addBinding(node, Argument(name, node), reportRedef=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.iteritems():
if (not binding.used and not name in self.scope.globals
and isinstance(binding, Assignment)):
self.report(messages.UnusedVariable,
binding.source, name)
self.deferAssignment(checkUnusedAssignments)
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.
"""
# decorator_list is present as of Python 2.6
for deco in getattr(node, 'decorator_list', []):
self.handleNode(deco, node)
for baseNode in node.bases:
self.handleNode(baseNode, node)
self.pushClassScope()
for stmt in node.body:
self.handleNode(stmt, node)
self.popScope()
self.addBinding(node, Binding(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):
# AugAssign is awkward: must set the context explicitly and visit twice,
# once with AugLoad context, once with AugStore context
node.target.ctx = _ast.AugLoad()
self.handleNode(node.target, node)
self.handleNode(node.value, node)
node.target.ctx = _ast.AugStore()
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.import_starred = True
self.report(messages.ImportStarUsed, node.lineno, node.col_offset, node.module)
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.lineno, node.col_offset)
self.add_binding(node, importation)
def visit_Global(self, node):
'''
Keep track of global declarations.
'''
scope = self.scope
if isinstance(scope, FunctionScope):
scope.globals.update(dict.fromkeys(node.names))
def visit_ClassDef(self, node):
try:
decorators = node.decorator_list
except AttributeError:
# Use .decorators for Python 2.5 compatibility
decorators = getattr(node, 'decorators', [])
self.visit_nodes(decorators)
self.add_binding(node, Assignment(node.name, node))
self.visit_nodes(node.bases)
self.push_class_scope()
self.visit_nodes(node.body)
self.pop_scope()
def visit_excepthandler(self, node):
if node.type is not None:
self.visit(node.type)
if node.name is not None:
self.assign_vars(node.name)
self.visit_nodes(node.body)
visit_ExceptHandler = visit_excepthandler # in 2.6, this was CamelCased
def flatten(self, nodes):
if isinstance(nodes, ast.Attribute):
self.visit(nodes)
return []
elif isinstance(nodes, ast.Subscript):
self.visit(nodes.value)
self.visit(nodes.slice)
return []
elif isinstance(nodes, ast.Name):
return [nodes]
elif isinstance(nodes, (ast.Tuple, ast.List)):
return self.flatten(nodes.elts)
flattened_nodes = []
for node in nodes:
if hasattr(node, 'elts'):
flattened_nodes += self.flatten(node.elts)
elif node is not None:
flattened_nodes += self.flatten(node)
return flattened_nodes
def add_binding(self, node, value, report_redef=True):
line, col, scope, name = node.lineno, node.col_offset, self.scope, value.name
# Check for a redefined function
func = scope.get(name)
if (isinstance(func, FunctionDefinition) and isinstance(value, FunctionDefinition)):
# Property-decorated functions (@x.setter) should have duplicate names
if not value._property_decorator:
self.report(messages.RedefinedFunction, line, name, func.source.lineno)
# Check for redefining an unused import
if report_redef and not isinstance(scope, ClassScope):
for up_scope in self.scope_stack[::-1]:
upval = up_scope.get(name)
if isinstance(upval, Importation) and not upval.used:
self.report(messages.RedefinedWhileUnused, line, col, name, upval.source.lineno)
# Check for "del undefined_name"
if isinstance(value, UnBinding):
try:
del scope[name]
except KeyError:
self.report(messages.UndefinedName, line, col, name)
else:
scope[name] = value
def visit(self, node):
if not isinstance(node, allowed_before_future):
self.futures_allowed = False
return super(Checker, self).visit(node)
def visit_nodes(self, nodes):
try:
nodes = list(getattr(nodes, 'elts', nodes))
except TypeError:
nodes = [nodes]
for node in nodes:
self.visit(node)
def is_builtin(self, name):
if hasattr(__builtin__, name):
return True
if name in defined_names:
return True
if name in self.builtins:
return True
return False
importation.used = (self.scope, node)
self.addBinding(node, importation)

View File

@@ -3,75 +3,94 @@
class Message(object):
message = ''
message_args = ()
def __init__(self, filename, lineno, col = None):
def __init__(self, filename, loc, use_column=True):
self.filename = filename
self.lineno = lineno
self.col = col
self.lineno = loc.lineno
self.col = getattr(loc, 'col_offset', None) if use_column else None
def __str__(self):
if self.col is not None:
return '%s:%s(%d): %s' % (self.filename, self.lineno, self.col, self.message % self.message_args)
else:
return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args)
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, lineno, name):
Message.__init__(self, filename, lineno)
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc, use_column=False)
self.message_args = (name,)
class RedefinedWhileUnused(Message):
message = 'redefinition of unused %r from line %r'
def __init__(self, filename, lineno, col, name, orig_lineno):
Message.__init__(self, filename, lineno)
self.message_args = (name, orig_lineno)
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, lineno, col, name, orig_lineno):
Message.__init__(self, filename, lineno, col)
self.message_args = (name, orig_lineno)
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, lineno, col, modname):
Message.__init__(self, filename, lineno, col)
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, lineno, col, name):
Message.__init__(self, filename, lineno, col)
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
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, lineno, col, name, orig_lineno, orig_col):
Message.__init__(self, filename, lineno)
self.message_args = (name, orig_lineno)
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, lineno, col, name):
Message.__init__(self, filename, lineno, col)
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class RedefinedFunction(Message):
message = 'redefinition of function %r from line %r'
def __init__(self, filename, lineno, name, orig_lineno):
Message.__init__(self, filename, lineno)
self.message_args = (name, orig_lineno)
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, lineno, col, names):
Message.__init__(self, filename, lineno)
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,)

View File

@@ -3,29 +3,55 @@
Implementation of the command-line I{pyflakes} tool.
"""
import _ast
import sys
import os
import _ast
checker = __import__('pyflakes.checker').checker
def check(codeString, filename):
"""
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}
@return: The number of warnings emitted.
@rtype: C{int}
"""
# First, compile into an AST and handle syntax errors.
try:
tree = compile(codeString, filename, 'exec', _ast.PyCF_ONLY_AST)
except (SyntaxError, IndentationError):
value = sys.exc_info()[1]
try:
(lineno, offset, line) = value[1][1:]
except IndexError:
print >> sys.stderr, 'could not compile %r' % (filename,)
return 1
if line.endswith("\n"):
line = line[:-1]
print >> sys.stderr, '%s:%d: could not compile' % (filename, lineno)
print >> sys.stderr, line
print >> sys.stderr, " " * (offset-2), "^"
tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
except SyntaxError, value:
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.
print >> sys.stderr, "%s: problem decoding source" % (filename, )
else:
line = text.splitlines()[-1]
if offset is not None:
offset = offset - (len(text) - len(line))
print >> sys.stderr, '%s:%d: %s' % (filename, lineno, msg)
print >> sys.stderr, line
if offset is not None:
print >> sys.stderr, " " * offset, "^"
return 1
else:
# Okay, it's syntactically valid. Now check it.
w = checker.Checker(tree, filename)
w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
for warning in w.messages:
@@ -39,12 +65,13 @@ def checkPath(filename):
@return: the number of warnings printed
"""
if os.path.exists(filename):
try:
return check(file(filename, 'U').read() + '\n', filename)
else:
print >> sys.stderr, '%s: no such file' % (filename,)
except IOError, msg:
print >> sys.stderr, "%s: %s" % (filename, msg.args[1])
return 1
def main():
warnings = 0
args = sys.argv[1:]

View File

@@ -1,15 +1,18 @@
import textwrap
import _ast
from twisted.trial import unittest
from pyflakes import checker, ast
from pyflakes import checker
class Test(unittest.TestCase):
def flakes(self, input, *expectedOutputs):
w = checker.Checker(ast.parse(textwrap.dedent(input)))
def flakes(self, input, *expectedOutputs, **kw):
ast = compile(textwrap.dedent(input), "<test>", "exec",
_ast.PyCF_ONLY_AST)
w = checker.Checker(ast, **kw)
outputs = [type(o) for o in w.messages]
expectedOutputs = list(expectedOutputs)
outputs.sort()

View File

@@ -51,6 +51,19 @@ class Test(harness.Test):
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
@@ -377,9 +390,55 @@ class Test(harness.Test):
def test_importStar(self):
self.flakes('from fu import *', m.ImportStarUsed)
def test_packageImport(self):
self.flakes('import fu.bar; fu.bar')
test_packageImport.todo = "this has been hacked to treat 'import fu.bar' as just 'import fu'"
"""
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')
@@ -401,6 +460,7 @@ class Test(harness.Test):
import fu
def a():
fu = 3
return fu
fu
''')
@@ -431,11 +491,6 @@ class Test(harness.Test):
''')
test_importingForImportError.todo = ''
def test_explicitlyPublic(self):
'''imports mentioned in __all__ are not unused'''
self.flakes('import fu; __all__ = ["fu"]')
test_explicitlyPublic.todo = "this would require importing the module or doing smarter parsing"
def test_importedInClass(self):
'''Imports in class scope can be used through self'''
self.flakes('''
@@ -449,6 +504,10 @@ class Test(harness.Test):
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):
"""
@@ -458,15 +517,82 @@ class Test(harness.Test):
x = 5
from __future__ import division
''', m.LateFutureImport)
self.flakes('''
from foo import bar
from __future__ import division
bar
''', m.LateFutureImport)
class Python24Tests(harness.Test):
class TestSpecialAll(harness.Test):
"""
Tests for checking of syntax which is valid in Python 2.4 and newer.
Tests for suppression of unused import warnings by C{__all__}.
"""
if version_info < (2, 4):
skip = "Python 2.4 required for generator expression and decorator tests."
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"]
''')
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_usedInGenExp(self):
@@ -510,3 +636,38 @@ class Python24Tests(harness.Test):
def f():
return "hello"
''', m.UndefinedName)
class Python26Tests(harness.Test):
"""
Tests for checking of syntax which is valid in PYthon 2.6 and newer.
"""
if version_info < (2, 6):
skip = "Python 2.6 required for class decorator tests."
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

@@ -1,4 +1,4 @@
# (c) 2005-2008 Divmod, Inc.
# (c) 2005-2010 Divmod, Inc.
# See LICENSE file for details
"""
@@ -74,6 +74,259 @@ class Test(harness.Test):
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_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 deefined 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,:]
''')
class TestUnusedAssignment(harness.Test):
"""
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_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
''')
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():
(x, y) = 1, 2
''')
def test_listUnpacking(self):
"""
Don't warn when a variable included in list unpacking is unused.
"""
self.flakes('''
def f():
[x, y] = [1, 2]
''')
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
''')
class Python25Test(harness.Test):
"""
@@ -117,6 +370,45 @@ class Python25Test(harness.Test):
''')
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
@@ -130,6 +422,36 @@ class Python25Test(harness.Test):
''')
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
@@ -226,9 +548,28 @@ class Python25Test(harness.Test):
pass
''', m.UndefinedName)
def test_listNestedListComprehension(self):
class Python27Test(harness.Test):
"""
Tests for checking of syntax only available in Python 2.7 and newer.
"""
if version_info < (2, 7):
skip = "Python 2.7 required for dict/set comprehension tests"
def test_dictComprehension(self):
"""
Dict comprehensions are properly handled.
"""
self.flakes('''
root = [['213', '123'], ['4354']]
foo = [int(c) for group in root for c in group]
a = {1: x for x in range(10)}
''')
def test_setComprehensionAndLiteral(self):
"""
Set comprehensions are properly handled.
"""
self.flakes('''
a = {1, 2, 3}
b = {x for x in range(10)}
''')

View File

@@ -44,5 +44,142 @@ class CheckTests(TestCase):
"""
err = StringIO()
count = withStderrTo(err, lambda: checkPath('extremo'))
self.assertEquals(err.getvalue(), 'extremo: no such file\n')
self.assertEquals(err.getvalue(), 'extremo: No such file or directory\n')
self.assertEquals(count, 1)
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
exc = self.assertRaises(SyntaxError, evaluate, source)
self.assertTrue(exc.text.count('\n') > 1)
sourcePath = FilePath(self.mktemp())
sourcePath.setContent(source)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEqual(count, 1)
self.assertEqual(
err.getvalue(),
"""\
%s:8: invalid syntax
'''quux'''
^
""" % (sourcePath.path,))
def test_eofSyntaxError(self):
"""
The error reported for source files which end prematurely causing a
syntax error reflects the cause for the syntax error.
"""
source = "def foo("
sourcePath = FilePath(self.mktemp())
sourcePath.setContent(source)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEqual(count, 1)
self.assertEqual(
err.getvalue(),
"""\
%s:1: unexpected EOF while parsing
def foo(
^
""" % (sourcePath.path,))
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 = FilePath(self.mktemp())
sourcePath.setContent(source)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEqual(count, 1)
self.assertEqual(
err.getvalue(),
"""\
%s:1: non-default argument follows default argument
def foo(bar=baz, bax):
""" % (sourcePath.path,))
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 = FilePath(self.mktemp())
sourcePath.setContent(source)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEqual(count, 1)
self.assertEqual(
err.getvalue(),
"""\
%s:1: non-keyword arg after keyword arg
foo(bar=baz, bax)
""" % (sourcePath.path,))
def test_permissionDenied(self):
"""
If the a source file is not readable, this is reported on standard
error.
"""
sourcePath = FilePath(self.mktemp())
sourcePath.setContent('')
sourcePath.chmod(0)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEquals(count, 1)
self.assertEquals(
err.getvalue(), "%s: Permission denied\n" % (sourcePath.path,))
def test_misencodedFile(self):
"""
If a source file contains bytes which cannot be decoded, this is
reported on stderr.
"""
source = u"""\
# coding: ascii
x = "\N{SNOWMAN}"
""".encode('utf-8')
sourcePath = FilePath(self.mktemp())
sourcePath.setContent(source)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEquals(count, 1)
self.assertEquals(
err.getvalue(), "%s: problem decoding source\n" % (sourcePath.path,))

View File

@@ -1,7 +1,9 @@
from sys import version_info
from _ast import PyCF_ONLY_AST
from pyflakes import messages as m
from twisted.trial.unittest import TestCase
from pyflakes import messages as m, checker
from pyflakes.test import harness
@@ -24,9 +26,40 @@ class Test(harness.Test):
def test_builtins(self):
self.flakes('range(10)')
def test_magic_globals(self):
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)
@@ -54,6 +87,16 @@ class Test(harness.Test):
''')
test_definedByGlobal.todo = ''
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)
@@ -91,6 +134,7 @@ class Test(harness.Test):
def fun():
a
a = 2
return a
''', m.UndefinedLocal)
def test_laterRedefinedGlobalFromNestedScope2(self):
@@ -106,6 +150,26 @@ class Test(harness.Test):
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)
@@ -124,6 +188,9 @@ class Test(harness.Test):
def c():
x
x = 3
return x
return x
return x
''', m.UndefinedLocal).messages[0]
self.assertEqual(exc.message_args, ('x', 5))
@@ -139,6 +206,8 @@ class Test(harness.Test):
def fun2():
a
a = 1
return a
return a
''', m.UndefinedLocal)
def test_nestedClass(self):
@@ -161,18 +230,16 @@ class Test(harness.Test):
class C:
bar = foo
foo = 456
return foo
f()
''', m.UndefinedName)
class Python24Test(harness.Test):
"""
Tests for checking of syntax which is valid in Python 2.4 and newer.
"""
if version_info < (2, 4):
skip = "Python 2.4 required for generator expression tests."
def test_definedAsStarArgs(self):
'''star and double-star arg names are defined'''
self.flakes('''
def f(a, *b, **c):
print a, b, c
''')
def test_definedInGenExp(self):
"""
@@ -180,3 +247,19 @@ class Python24Test(harness.Test):
warnings.
"""
self.flakes('(a for a in xrange(10) if a)')
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

@@ -1,19 +1,28 @@
#!/usr/bin/python
# (c) 2005 Divmod, Inc. See LICENSE file for details
# (c) 2005-2009 Divmod, Inc. See LICENSE file for details
from distutils.core import setup
setup(
name="pyflakes",
license="MIT",
version="0.2.1",
version="0.4.0",
description="passive checker of Python programs",
author="Phil Frost",
maintainer="Moe Aboulkheir",
maintainer_email="moe@divmod.com",
url="http://www.divmod.org/projects/pyflakes",
packages=["pyflakes", "pyflakes.scripts"],
url="http://www.divmod.org/trac/wiki/DivmodPyflakes",
packages=["pyflakes", "pyflakes.scripts", "pyflakes.test"],
scripts=["bin/pyflakes"],
long_description="""Pyflakes is program to analyze Python programs and detect various errors. It
works by parsing the source file, not importing it, so it is safe to use on
modules with side effects. It's also much faster.""")
modules with side effects. It's also much faster.""",
classifiers=[
"Development Status :: 6 - Mature",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Topic :: Software Development",
"Topic :: Utilities",
])