Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First load of pyflakes in

  • Loading branch information...
commit 742e6fd47ceddbc62e7b6a65fdd2e903d4bc1a62 0 parents
@mitechie authored
11 README.rst
@@ -0,0 +1,11 @@
+Pyflakes
+==========
+
+This is just a manual dump of the vim pyflakes plugin from:
+http://www.vim.org/scripts/script.php?script_id=2441
+
+The purpose is to try to make this compatible with pathogen plugin. So creating
+the dir structure and hopefully this means we can just keep the norm and git
+clone the repo into the bundle directory and things will load up magically.
+
+
72 ftplugin/python/README.rst
@@ -0,0 +1,72 @@
+pyflakes-vim
+============
+
+A Vim plugin for checking Python code on the fly.
+
+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, you'll need PyFlakes on your PYTHONPATH somewhere. I recommend
+getting my PyFlakes_ fork, which uses the ``_ast`` module new to Python 2.5,
+and is faster and more current than PyFlakes' old usage of the deprecated
+``compiler`` module.
+
+.. _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
+
+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
+
+.. _signs: http://www.vim.org/htmldoc/sign.html
+
+Changelog
+---------
+
+Please see http://www.vim.org/scripts/script.php?script_id=2441 for a history of
+all changes.
+
300 ftplugin/python/pyflakes.vim
@@ -0,0 +1,300 @@
+" pyflakes.vim - A script to highlight Python code on the fly with warnings
+" from Pyflakes, a Python lint tool.
+"
+" Place this script and the accompanying pyflakes directory in
+" .vim/ftplugin/python.
+"
+" See README for additional installation and information.
+"
+" Thanks to matlib.vim for ideas/code on interactive linting.
+"
+" Maintainer: Kevin Watters <kevin.watters@gmail.com>
+" Version: 0.1
+
+if exists("b:did_pyflakes_plugin")
+ finish " only load once
+else
+ let b:did_pyflakes_plugin = 1
+endif
+
+if !exists('g:pyflakes_builtins')
+ let g:pyflakes_builtins = []
+endif
+
+if !exists("b:did_python_init")
+ let b:did_python_init = 0
+
+ if !has('python')
+ echoerr "Error: the pyflakes.vim plugin requires Vim to be compiled with +python"
+ finish
+ endif
+
+ python << EOF
+import vim
+import os.path
+import sys
+
+if sys.version_info[:2] < (2, 5):
+ raise AssertionError('Vim must be compiled with Python 2.5 or higher; you have ' + sys.version)
+
+# get the directory this script is in: the pyflakes python module should be installed there.
+scriptdir = os.path.join(os.path.dirname(vim.eval('expand("<sfile>")')), 'pyflakes')
+sys.path.insert(0, scriptdir)
+
+from pyflakes import checker, ast, messages
+from operator import attrgetter
+import re
+
+class SyntaxError(messages.Message):
+ message = 'could not compile: %s'
+ def __init__(self, filename, lineno, col, message):
+ messages.Message.__init__(self, filename, lineno, col)
+ self.message_args = (message,)
+
+class blackhole(object):
+ write = flush = lambda *a, **k: None
+
+def check(buffer):
+ filename = buffer.name
+ contents = buffer[:]
+
+ # shebang usually found at the top of the file, followed by source code encoding marker.
+ # 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:]
+ break
+ contents = '\n'.join(contents) + '\n'
+
+ vimenc = vim.eval('&encoding')
+ if vimenc:
+ contents = contents.decode(vimenc)
+
+ builtins = []
+ try:
+ builtins = eval(vim.eval('string(g:pyflakes_builtins)'))
+ except Exception:
+ pass
+
+ try:
+ # TODO: use warnings filters instead of ignoring stderr
+ old_stderr, sys.stderr = sys.stderr, blackhole()
+ try:
+ tree = ast.parse(contents, filename)
+ finally:
+ sys.stderr = old_stderr
+ except:
+ try:
+ value = sys.exc_info()[1]
+ lineno, offset, line = value[1][1:]
+ except IndexError:
+ lineno, offset, line = 1, 0, ''
+ if line and line.endswith("\n"):
+ line = line[:-1]
+
+ return [SyntaxError(filename, lineno, offset, str(value))]
+ else:
+ w = checker.Checker(tree, filename, builtins = builtins)
+ w.messages.sort(key = attrgetter('lineno'))
+ return w.messages
+
+
+def vim_quote(s):
+ return s.replace("'", "''")
+EOF
+ let b:did_python_init = 1
+endif
+
+if !b:did_python_init
+ finish
+endif
+
+au BufLeave <buffer> call s:ClearPyflakes()
+
+au BufEnter <buffer> call s:RunPyflakes()
+au InsertLeave <buffer> call s:RunPyflakes()
+au InsertEnter <buffer> call s:RunPyflakes()
+au BufWritePost <buffer> call s:RunPyflakes()
+
+au CursorHold <buffer> call s:RunPyflakes()
+au CursorHoldI <buffer> call s:RunPyflakes()
+
+au CursorHold <buffer> call s:GetPyflakesMessage()
+au CursorMoved <buffer> call s:GetPyflakesMessage()
+
+if !exists("*s:PyflakesUpdate")
+ function s:PyflakesUpdate()
+ silent call s:RunPyflakes()
+ call s:GetPyflakesMessage()
+ endfunction
+endif
+
+" Call this function in your .vimrc to update PyFlakes
+if !exists(":PyflakesUpdate")
+ command PyflakesUpdate :call s:PyflakesUpdate()
+endif
+
+" Hook common text manipulation commands to update PyFlakes
+" TODO: is there a more general "text op" autocommand we could register
+" for here?
+noremap <buffer><silent> dd dd:PyflakesUpdate<CR>
+noremap <buffer><silent> dw dw:PyflakesUpdate<CR>
+noremap <buffer><silent> u u:PyflakesUpdate<CR>
+noremap <buffer><silent> <C-R> <C-R>:PyflakesUpdate<CR>
+
+" WideMsg() prints [long] message up to (&columns-1) length
+" guaranteed without "Press Enter" prompt.
+if !exists("*s:WideMsg")
+ function s:WideMsg(msg)
+ let x=&ruler | let y=&showcmd
+ set noruler noshowcmd
+ redraw
+ echo a:msg
+ let &ruler=x | let &showcmd=y
+ endfun
+endif
+
+if !exists("*s:GetQuickFixStackCount")
+ function s:GetQuickFixStackCount()
+ let l:stack_count = 0
+ try
+ silent colder 9
+ catch /E380:/
+ endtry
+
+ try
+ for i in range(9)
+ silent cnewer
+ let l:stack_count = l:stack_count + 1
+ endfor
+ catch /E381:/
+ return l:stack_count
+ endtry
+ endfunction
+endif
+
+if !exists("*s:ActivatePyflakesQuickFixWindow")
+ function s:ActivatePyflakesQuickFixWindow()
+ try
+ silent colder 9 " go to the bottom of quickfix stack
+ catch /E380:/
+ endtry
+
+ if s:pyflakes_qf > 0
+ try
+ exe "silent cnewer " . s:pyflakes_qf
+ catch /E381:/
+ echoerr "Could not activate Pyflakes Quickfix Window."
+ endtry
+ endif
+ endfunction
+endif
+
+if !exists("*s:RunPyflakes")
+ function s:RunPyflakes()
+ highlight link PyFlakes SpellBad
+
+ if exists("b:cleared")
+ if b:cleared == 0
+ silent call s:ClearPyflakes()
+ let b:cleared = 1
+ endif
+ else
+ let b:cleared = 1
+ endif
+
+ let b:matched = []
+ let b:matchedlines = {}
+
+ let b:qf_list = []
+ let b:qf_window_count = -1
+
+ python << EOF
+for w in check(vim.current.buffer):
+ vim.command('let s:matchDict = {}')
+ vim.command("let s:matchDict['lineNum'] = " + str(w.lineno))
+ vim.command("let s:matchDict['message'] = '%s'" % vim_quote(w.message % w.message_args))
+ vim.command("let b:matchedlines[" + str(w.lineno) + "] = s:matchDict")
+
+ vim.command("let l:qf_item = {}")
+ vim.command("let l:qf_item.bufnr = bufnr('%')")
+ vim.command("let l:qf_item.filename = expand('%')")
+ vim.command("let l:qf_item.lnum = %s" % str(w.lineno))
+ 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):
+ # 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\@!')")
+ else:
+ # with a column number, highlight the first keyword there
+ vim.command(r"let s:mID = matchadd('PyFlakes', '^\%" + str(w.lineno) + r"l\_.\{-}\zs\k\+\k\@!\%>" + str(w.col) + r"c')")
+
+ vim.command("let l:qf_item.vcol = 1")
+ vim.command("let l:qf_item.col = %s" % str(w.col + 1))
+
+ vim.command("call add(b:matched, s:matchDict)")
+ vim.command("call add(b:qf_list, l:qf_item)")
+EOF
+ if exists("s:pyflakes_qf")
+ " if pyflakes quickfix window is already created, reuse it
+ call s:ActivatePyflakesQuickFixWindow()
+ call setqflist(b:qf_list, 'r')
+ else
+ " one pyflakes quickfix window for all buffer
+ call setqflist(b:qf_list, '')
+ let s:pyflakes_qf = s:GetQuickFixStackCount()
+ endif
+ let b:cleared = 0
+ endfunction
+end
+
+" keep track of whether or not we are showing a message
+let b:showing_message = 0
+
+if !exists("*s:GetPyflakesMessage")
+ function s:GetPyflakesMessage()
+ let s:cursorPos = getpos(".")
+
+ " Bail if RunPyflakes hasn't been called yet.
+ if !exists('b:matchedlines')
+ return
+ endif
+
+ " if there's a message for the line the cursor is currently on, echo
+ " it to the console
+ if has_key(b:matchedlines, s:cursorPos[1])
+ let s:pyflakesMatch = get(b:matchedlines, s:cursorPos[1])
+ call s:WideMsg(s:pyflakesMatch['message'])
+ let b:showing_message = 1
+ return
+ endif
+
+ " otherwise, if we're showing a message, clear it
+ if b:showing_message == 1
+ echo
+ let b:showing_message = 0
+ endif
+ endfunction
+endif
+
+if !exists('*s:ClearPyflakes')
+ function s:ClearPyflakes()
+ let s:matches = getmatches()
+ for s:matchId in s:matches
+ if s:matchId['group'] == 'PyFlakes'
+ call matchdelete(s:matchId['id'])
+ endif
+ endfor
+ let b:matched = []
+ let b:matchedlines = {}
+ let b:cleared = 1
+ endfunction
+endif
+
21 ftplugin/python/pyflakes/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) 2005 Divmod, Inc., http://www.divmod.com/
+
+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.
36 ftplugin/python/pyflakes/README.rst
@@ -0,0 +1,36 @@
+pyflakes
+========
+
+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.
+
+.. _PyFlakes: http://http://www.divmod.org/trac/wiki/DivmodPyflakes
+
+TODO
+----
+
+Importing several modules from the same package results in unnecessary warnings:
+
+::
+
+ 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
+
11 ftplugin/python/pyflakes/TODO
@@ -0,0 +1,11 @@
+ - 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()
+
0  ftplugin/python/pyflakes/pyflakes/__init__.py
No changes.
311 ftplugin/python/pyflakes/pyflakes/ast.py
@@ -0,0 +1,311 @@
+# -*- coding: utf-8 -*-
+"""
+ ast
+ ~~~
+
+ The `ast` module helps Python applications to process trees of the Python
+ abstract syntax grammar. The abstract syntax itself might change with
+ each Python release; this module helps to find out programmatically what
+ the current grammar looks like and allows modifications of it.
+
+ An abstract syntax tree can be generated by passing `ast.PyCF_ONLY_AST` as
+ a flag to the `compile()` builtin function or by using the `parse()`
+ function from this module. The result will be a tree of objects whose
+ classes all inherit from `ast.AST`.
+
+ A modified abstract syntax tree can be compiled into a Python code object
+ using the built-in `compile()` function.
+
+ Additionally various helper functions are provided that make working with
+ the trees simpler. The main intention of the helper functions and this
+ module in general is to provide an easy to use interface for libraries
+ that work tightly with the python syntax (template engines for example).
+
+
+ :copyright: Copyright 2008 by Armin Ronacher.
+ :license: Python License.
+"""
+from _ast import *
+from _ast import __version__
+
+
+def parse(expr, filename='<unknown>', mode='exec'):
+ """
+ Parse an expression into an AST node.
+ Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST).
+ """
+ return compile(expr, filename, mode, PyCF_ONLY_AST)
+
+
+def literal_eval(node_or_string):
+ """
+ Safely evaluate an expression node or a string containing a Python
+ expression. The string or node provided may only consist of the following
+ Python literal structures: strings, numbers, tuples, lists, dicts, booleans,
+ and None.
+ """
+ _safe_names = {'None': None, 'True': True, 'False': False}
+ if isinstance(node_or_string, basestring):
+ node_or_string = parse(node_or_string, mode='eval')
+ if isinstance(node_or_string, Expression):
+ node_or_string = node_or_string.body
+ def _convert(node):
+ if isinstance(node, Str):
+ return node.s
+ elif isinstance(node, Num):
+ return node.n
+ elif isinstance(node, Tuple):
+ return tuple(map(_convert, node.elts))
+ elif isinstance(node, List):
+ return list(map(_convert, node.elts))
+ elif isinstance(node, Dict):
+ return dict((_convert(k), _convert(v)) for k, v
+ in zip(node.keys, node.values))
+ elif isinstance(node, Name):
+ if node.id in _safe_names:
+ return _safe_names[node.id]
+ raise ValueError('malformed string')
+ return _convert(node_or_string)
+
+
+def dump(node, annotate_fields=True, include_attributes=False):
+ """
+ Return a formatted dump of the tree in *node*. This is mainly useful for
+ debugging purposes. The returned string will show the names and the values
+ for fields. This makes the code impossible to evaluate, so if evaluation is
+ wanted *annotate_fields* must be set to False. Attributes such as line
+ numbers and column offsets are not dumped by default. If this is wanted,
+ *include_attributes* can be set to True.
+ """
+ def _format(node):
+ if isinstance(node, AST):
+ fields = [(a, _format(b)) for a, b in iter_fields(node)]
+ rv = '%s(%s' % (node.__class__.__name__, ', '.join(
+ ('%s=%s' % field for field in fields)
+ if annotate_fields else
+ (b for a, b in fields)
+ ))
+ if include_attributes and node._attributes:
+ rv += fields and ', ' or ' '
+ rv += ', '.join('%s=%s' % (a, _format(getattr(node, a)))
+ for a in node._attributes)
+ return rv + ')'
+ elif isinstance(node, list):
+ return '[%s]' % ', '.join(_format(x) for x in node)
+ return repr(node)
+ if not isinstance(node, AST):
+ raise TypeError('expected AST, got %r' % node.__class__.__name__)
+ return _format(node)
+
+
+def copy_location(new_node, old_node):
+ """
+ Copy source location (`lineno` and `col_offset` attributes) from
+ *old_node* to *new_node* if possible, and return *new_node*.
+ """
+ for attr in 'lineno', 'col_offset':
+ if attr in old_node._attributes and attr in new_node._attributes \
+ and hasattr(old_node, attr):
+ setattr(new_node, attr, getattr(old_node, attr))
+ return new_node
+
+
+def fix_missing_locations(node):
+ """
+ When you compile a node tree with compile(), the compiler expects lineno and
+ col_offset attributes for every node that supports them. This is rather
+ tedious to fill in for generated nodes, so this helper adds these attributes
+ recursively where not already set, by setting them to the values of the
+ parent node. It works recursively starting at *node*.
+ """
+ def _fix(node, lineno, col_offset):
+ if 'lineno' in node._attributes:
+ if not hasattr(node, 'lineno'):
+ node.lineno = lineno
+ else:
+ lineno = node.lineno
+ if 'col_offset' in node._attributes:
+ if not hasattr(node, 'col_offset'):
+ node.col_offset = col_offset
+ else:
+ col_offset = node.col_offset
+ for child in iter_child_nodes(node):
+ _fix(child, lineno, col_offset)
+ _fix(node, 1, 0)
+ return node
+
+def add_col_end(node):
+ def _fix(node, next):
+ children = list(iter_child_nodes(node))
+ for i, child in enumerate(children):
+ next_offset = children[i+1].col_offset if i < len(children) else next.col_offset
+ child.col_end = next_offset
+
+
+def increment_lineno(node, n=1):
+ """
+ Increment the line number of each node in the tree starting at *node* by *n*.
+ This is useful to "move code" to a different location in a file.
+ """
+ if 'lineno' in node._attributes:
+ node.lineno = getattr(node, 'lineno', 0) + n
+ for child in walk(node):
+ if 'lineno' in child._attributes:
+ child.lineno = getattr(child, 'lineno', 0) + n
+ return node
+
+
+def iter_fields(node):
+ """
+ Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields``
+ that is present on *node*.
+ """
+ if node._fields is None:
+ return
+
+ for field in node._fields:
+ try:
+ yield field, getattr(node, field)
+ except AttributeError:
+ pass
+
+
+def iter_child_nodes(node):
+ """
+ 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, field in iter_fields(node):
+ if isinstance(field, AST):
+ yield field
+ elif isinstance(field, list):
+ for item in field:
+ if isinstance(item, AST):
+ yield item
+
+
+def get_docstring(node, clean=True):
+ """
+ Return the docstring for the given node or None if no docstring can
+ be found. If the node provided does not have docstrings a TypeError
+ will be raised.
+ """
+ if not isinstance(node, (FunctionDef, ClassDef, Module)):
+ raise TypeError("%r can't have docstrings" % node.__class__.__name__)
+ if node.body and isinstance(node.body[0], Expr) and \
+ isinstance(node.body[0].value, Str):
+ if clean:
+ import inspect
+ return inspect.cleandoc(node.body[0].value.s)
+ return node.body[0].value.s
+
+
+def walk(node):
+ """
+ Recursively yield all child nodes of *node*, in no specified order. This is
+ useful if you only want to modify nodes in place and don't care about the
+ context.
+ """
+ from collections import deque
+ todo = deque([node])
+ while todo:
+ node = todo.popleft()
+ todo.extend(iter_child_nodes(node))
+ yield node
+
+
+class NodeVisitor(object):
+ """
+ A node visitor base class that walks the abstract syntax tree and calls a
+ visitor function for every node found. This function may return a value
+ which is forwarded by the `visit` method.
+
+ This class is meant to be subclassed, with the subclass adding visitor
+ methods.
+
+ Per default the visitor functions for the nodes are ``'visit_'`` +
+ class name of the node. So a `TryFinally` node visit function would
+ be `visit_TryFinally`. This behavior can be changed by overriding
+ the `visit` method. If no visitor function exists for a node
+ (return value `None`) the `generic_visit` visitor is used instead.
+
+ Don't use the `NodeVisitor` if you want to apply changes to nodes during
+ traversing. For this a special visitor exists (`NodeTransformer`) that
+ allows modifications.
+ """
+
+ def visit(self, node):
+ """Visit a node."""
+ method = 'visit_' + node.__class__.__name__
+ visitor = getattr(self, method, self.generic_visit)
+ return visitor(node)
+
+ def generic_visit(self, node):
+ """Called if no explicit visitor function exists for a node."""
+ for field, value in iter_fields(node):
+ if isinstance(value, list):
+ for item in value:
+ if isinstance(item, AST):
+ self.visit(item)
+ elif isinstance(value, AST):
+ self.visit(value)
+
+
+class NodeTransformer(NodeVisitor):
+ """
+ A :class:`NodeVisitor` subclass that walks the abstract syntax tree and
+ allows modification of nodes.
+
+ The `NodeTransformer` will walk the AST and use the return value of the
+ visitor methods to replace or remove the old node. If the return value of
+ the visitor method is ``None``, the node will be removed from its location,
+ otherwise it is replaced with the return value. The return value may be the
+ original node in which case no replacement takes place.
+
+ Here is an example transformer that rewrites all occurrences of name lookups
+ (``foo``) to ``data['foo']``::
+
+ class RewriteName(NodeTransformer):
+
+ def visit_Name(self, node):
+ return copy_location(Subscript(
+ value=Name(id='data', ctx=Load()),
+ slice=Index(value=Str(s=node.id)),
+ ctx=node.ctx
+ ), node)
+
+ Keep in mind that if the node you're operating on has child nodes you must
+ either transform the child nodes yourself or call the :meth:`generic_visit`
+ method for the node first.
+
+ For nodes that were part of a collection of statements (that applies to all
+ statement nodes), the visitor may also return a list of nodes rather than
+ just a single node.
+
+ Usually you use the transformer like this::
+
+ node = YourTransformer().visit(node)
+ """
+
+ def generic_visit(self, node):
+ for field, old_value in iter_fields(node):
+ old_value = getattr(node, field, None)
+ if isinstance(old_value, list):
+ new_values = []
+ for value in old_value:
+ if isinstance(value, AST):
+ value = self.visit(value)
+ if value is None:
+ continue
+ elif not isinstance(value, AST):
+ new_values.extend(value)
+ continue
+ new_values.append(value)
+ old_value[:] = new_values
+ elif isinstance(old_value, AST):
+ new_node = self.visit(old_value)
+ if new_node is None:
+ delattr(node, field)
+ else:
+ setattr(node, field, new_node)
+ return node
408 ftplugin/python/pyflakes/pyflakes/checker.py
@@ -0,0 +1,408 @@
+import ast
+from pyflakes import messages
+import __builtin__
+
+
+allowed_before_future = (ast.Module, ast.ImportFrom, ast.Expr, ast.Str)
+defined_names = set(('__file__', '__builtins__'))
+
+class Binding(object):
+ """
+ @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):
+ def __init__(self, name, source):
+ name = name.split('.')[0]
+ super(Importation, self).__init__(name, source)
+
+class Assignment(Binding):
+ pass
+
+class FunctionDefinition(Binding):
+ _property_decorator = False
+
+
+class Scope(dict):
+ import_starred = 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
+
+
+
+class FunctionScope(Scope):
+ """
+ I represent a name scope for a function.
+
+ @ivar globals: Names declared 'global' in this function.
+ """
+ def __init__(self):
+ super(FunctionScope, self).__init__()
+ self.globals = {}
+
+
+
+class ModuleScope(Scope):
+ pass
+
+class Checker(ast.NodeVisitor):
+ def __init__(self, tree, filename='(none)', builtins = None):
+ ast.NodeVisitor.__init__(self)
+
+ self.deferred = []
+ 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.check_dead_scopes()
+
+ def defer(self, callable):
+ '''Schedule something to be called after 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[:]) )
+
+ def check_dead_scopes(self):
+ # Check for modules that were imported but unused
+ for scope in self.dead_scopes:
+ for importation in scope.itervalues():
+ if isinstance(importation, Importation) and not importation.used:
+ self.report(messages.UnusedImport, importation.source.lineno, importation.name)
+
+ def push_function_scope(self):
+ self.scope_stack.append(FunctionScope())
+
+ def push_class_scope(self):
+ self.scope_stack.append(ClassScope())
+
+ def pop_scope(self):
+ scope = self.scope_stack.pop()
+ self.dead_scopes.append(scope)
+
+ @property
+ def scope(self):
+ return self.scope_stack[-1]
+
+ def report(self, message_class, *args, **kwargs):
+ self.messages.append(message_class(self.filename, *args, **kwargs))
+
+ 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):
+
+ try:
+ decorators = node.decorator_list
+ except AttributeError:
+ # Use .decorators for Python 2.5 compatibility
+ decorators = node.decorators
+
+ self.visit_nodes(decorators)
+
+ # Check for property decorator
+ func_def = FunctionDefinition(node.name, node)
+
+ for decorator in decorators:
+ if getattr(decorator, 'attr', None) in ('setter', 'deleter'):
+ func_def._property_decorator = True
+
+ self.add_binding(node, func_def)
+
+ self.visit_Lambda(node)
+
+ def visit_Lambda(self, node):
+ self.visit_nodes(node.args.defaults)
+
+ def run_function():
+ self.push_function_scope()
+
+ # 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)
+
+ 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()
+
+ self.defer(run_function)
+
+ def visit_Name(self, node):
+ '''
+ Locate names in locals / function / globals scopes.
+ '''
+ scope, name = self.scope, node.id
+
+ # try local scope
+ import_starred = scope.import_starred
+ try:
+ scope[name].used = (scope, node.lineno, node.col_offset)
+ except KeyError:
+ pass
+ else:
+ return
+
+ # 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
+ try:
+ func_scope[name].used = (scope, node.lineno, node.col_offset)
+ 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)
+
+ def assign_vars(self, targets, report_redef=True):
+ scope = self.scope
+
+ for target in self.flatten(targets):
+ name = target.id
+ # if the name hasn't already been defined in the current scope
+ if isinstance(scope, FunctionScope) and name not in scope:
+ # for each function or module scope above us
+ for upscope in self.scope_stack[:-1]:
+ if not isinstance(upscope, (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)
+
+ 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]
+ 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])
+ else:
+ self.futures_allowed = 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)
+ 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
+
77 ftplugin/python/pyflakes/pyflakes/messages.py
@@ -0,0 +1,77 @@
+# (c) 2005 Divmod, Inc. See LICENSE file for details
+
+class Message(object):
+ message = ''
+ message_args = ()
+ def __init__(self, filename, lineno, col = None):
+ self.filename = filename
+ self.lineno = lineno
+ self.col = col
+ 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)
+
+
+class UnusedImport(Message):
+ message = '%r imported but unused'
+ def __init__(self, filename, lineno, name):
+ Message.__init__(self, filename, lineno)
+ 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)
+
+
+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)
+
+
+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)
+ self.message_args = (modname,)
+
+
+class UndefinedName(Message):
+ message = 'undefined name %r'
+ def __init__(self, filename, lineno, col, name):
+ Message.__init__(self, filename, lineno, col)
+ 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)
+
+
+class DuplicateArgument(Message):
+ message = 'duplicate argument %r in function definition'
+ def __init__(self, filename, lineno, col, name):
+ Message.__init__(self, filename, lineno, col)
+ 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)
+
+
+class LateFutureImport(Message):
+ message = 'future import(s) %r after other statements'
+ def __init__(self, filename, lineno, col, names):
+ Message.__init__(self, filename, lineno)
+ self.message_args = (names,)
0  ftplugin/python/pyflakes/pyflakes/scripts/__init__.py
No changes.
63 ftplugin/python/pyflakes/pyflakes/scripts/pyflakes.py
@@ -0,0 +1,63 @@
+
+"""
+Implementation of the command-line I{pyflakes} tool.
+"""
+
+import _ast
+import sys
+import os
+
+checker = __import__('pyflakes.checker').checker
+
+def check(codeString, filename):
+ 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), "^"
+ return 1
+ else:
+ w = checker.Checker(tree, filename)
+ w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
+ for warning in w.messages:
+ print warning
+ return len(w.messages)
+
+
+def checkPath(filename):
+ """
+ Check the given path, printing out any warnings detected.
+
+ @return: the number of warnings printed
+ """
+ if os.path.exists(filename):
+ return check(file(filename, 'U').read() + '\n', filename)
+ else:
+ print >> sys.stderr, '%s: no such file' % (filename,)
+ return 1
+
+def main():
+ warnings = 0
+ args = sys.argv[1:]
+ if args:
+ for arg in args:
+ if os.path.isdir(arg):
+ for dirpath, dirnames, filenames in os.walk(arg):
+ for filename in filenames:
+ if filename.endswith('.py'):
+ warnings += checkPath(os.path.join(dirpath, filename))
+ else:
+ warnings += checkPath(arg)
+ else:
+ warnings += check(sys.stdin.read(), '<stdin>')
+
+ raise SystemExit(warnings > 0)
0  ftplugin/python/pyflakes/pyflakes/test/__init__.py
No changes.
24 ftplugin/python/pyflakes/pyflakes/test/harness.py
@@ -0,0 +1,24 @@
+
+import textwrap
+
+from twisted.trial import unittest
+
+from pyflakes import checker, ast
+
+
+class Test(unittest.TestCase):
+
+ def flakes(self, input, *expectedOutputs):
+ w = checker.Checker(ast.parse(textwrap.dedent(input)))
+ outputs = [type(o) for o in w.messages]
+ expectedOutputs = list(expectedOutputs)
+ outputs.sort()
+ expectedOutputs.sort()
+ self.assert_(outputs == expectedOutputs, '''\
+for input:
+%s
+expected outputs:
+%s
+but got:
+%s''' % (input, repr(expectedOutputs), '\n'.join([str(o) for o in w.messages])))
+ return w
512 ftplugin/python/pyflakes/pyflakes/test/test_imports.py
@@ -0,0 +1,512 @@
+
+from sys import version_info
+
+from pyflakes import messages as m
+from pyflakes.test import harness
+
+class Test(harness.Test):
+
+ 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')
+
+ def test_redefinedWhileUnused(self):
+ self.flakes('import fu; fu = 3', m.RedefinedWhileUnused)
+ self.flakes('import fu; del fu', m.RedefinedWhileUnused)
+ self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused)
+ self.flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused)
+
+ 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_redefinedByClass(self):
+ self.flakes('''
+ import fu
+ class fu:
+ pass
+ ''', m.RedefinedWhileUnused)
+
+ 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)
+
+ 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.RedefinedWhileUnused)
+
+ 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)
+
+ 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):
+ self.flakes('''
+ import fu
+ try: pass
+ except Exception, fu: pass
+ ''', 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.RedefinedWhileUnused)
+
+
+ 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)
+
+ def test_usedInBackquote(self):
+ self.flakes('import fu; `fu`')
+
+ def test_usedInExec(self):
+ self.flakes('import fu; exec "print 1" in fu.bar')
+
+ def test_usedInLambda(self):
+ self.flakes('import fu; lambda: fu')
+
+ def test_shadowedByLambda(self):
+ self.flakes('import fu; lambda fu: fu', m.UnusedImport)
+
+ 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):
+ self.flakes('import fu.bar; fu.bar')
+ test_packageImport.todo = "this has been hacked to treat 'import fu.bar' as just 'import fu'"
+
+ 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
+ ''')
+ test_tryingMultipleImports.todo = ''
+
+ def test_nonGlobalDoesNotRedefine(self):
+ self.flakes('''
+ import fu
+ def a():
+ fu = 3
+ 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')
+
+ def test_importingForImportError(self):
+ self.flakes('''
+ try:
+ import fu
+ except ImportError:
+ pass
+ ''')
+ 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('''
+ class c:
+ import i
+ def __init__(self):
+ self.i
+ ''')
+ test_importedInClass.todo = 'requires evaluating attribute access'
+
+ def test_futureImport(self):
+ '''__future__ is special'''
+ self.flakes('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)
+
+
+
+class Python24Tests(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 and decorator tests."
+
+
+ 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)
+
+
+ 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)
234 ftplugin/python/pyflakes/pyflakes/test/test_other.py
@@ -0,0 +1,234 @@
+# (c) 2005-2008 Divmod, Inc.
+# See LICENSE file for details
+
+"""
+Tests for various Pyflakes behavior.
+"""
+
+from sys import version_info
+
+from pyflakes import messages as m
+from pyflakes.test import harness
+
+
+class Test(harness.Test):
+
+ 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.UndefinedName)
+ test_localReferencedBeforeAssignment.todo = 'this requires finding all assignments in the function body first'
+
+ 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.RedefinedFunction)
+
+ 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.RedefinedFunction)
+
+ 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)
+ ''')
+
+ def test_unaryPlus(self):
+ '''Don't die on unary +'''
+ self.flakes('+1')
+
+
+
+class Python25Test(harness.Test):
+ """
+ Tests for checking of syntax only available in Python 2.5 and newer.
+ """
+ if version_info < (2, 5):
+ skip = "Python 2.5 required for if-else and with tests"
+
+ 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_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_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)
+
+ def test_listNestedListComprehension(self):
+ self.flakes('''
+ root = [['213', '123'], ['4354']]
+ foo = [int(c) for group in root for c in group]
+ ''')
+
48 ftplugin/python/pyflakes/pyflakes/test/test_script.py
@@ -0,0 +1,48 @@
+
+"""
+Tests for L{pyflakes.scripts.pyflakes}.
+"""
+
+import sys
+from StringIO import StringIO
+
+from twisted.python.filepath import FilePath
+from twisted.trial.unittest import TestCase
+
+from pyflakes.scripts.pyflakes import checkPath
+
+def withStderrTo(stderr, f):
+ """
+ Call C{f} with C{sys.stderr} redirected to C{stderr}.
+ """
+ (outer, sys.stderr) = (sys.stderr, stderr)
+ try:
+ return f()
+ finally:
+ sys.stderr = outer
+
+
+
+class CheckTests(TestCase):
+ """
+ Tests for L{check} and L{checkPath} which check a file for flakes.
+ """
+ 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.mktemp()
+ FilePath(fName).setContent("def foo():\n\tpass\n\t")
+ self.assertFalse(checkPath(fName))
+
+
+ def test_checkPathNonExisting(self):
+ """
+ L{checkPath} handles non-existing files.
+ """
+ err = StringIO()
+ count = withStderrTo(err, lambda: checkPath('extremo'))
+ self.assertEquals(err.getvalue(), 'extremo: no such file\n')
+ self.assertEquals(count, 1)
182 ftplugin/python/pyflakes/pyflakes/test/test_undefined_names.py
@@ -0,0 +1,182 @@
+
+from sys import version_info
+
+from pyflakes import messages as m
+from pyflakes.test import harness
+
+
+class Test(harness.Test):
+ 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_magic_globals(self):
+ self.flakes('__file__')
+
+ 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)
+
+ def test_unpackedParameter(self):
+ '''Unpacked function parameters create bindings'''
+ self.flakes('''
+ def a((bar, baz)):
+ bar; baz
+ ''')
+
+ 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
+ ''')
+ test_definedByGlobal.todo = ''
+
+ 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
+ ''', 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
+ ''', 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
+ ''', 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
+ ''', m.UndefinedLocal)
+
+ 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
+
+ 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_definedInGenExp(self):
+ """
+ Using the loop variable of a generator expression results in no
+ warnings.
+ """
+ self.flakes('(a for a in xrange(10) if a)')
19 ftplugin/python/pyflakes/setup.py
@@ -0,0 +1,19 @@
+#!/usr/bin/python
+# (c) 2005 Divmod, Inc. See LICENSE file for details
+
+from distutils.core import setup
+
+setup(
+ name="pyflakes",
+ license="MIT",
+ version="0.2.1",
+ 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"],
+ 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.""")
124 ftplugin/python/quickfix.diff
@@ -0,0 +1,124 @@
+diff --git a/README.rst b/README.rst
+index 5f8467f..acff657 100644
+--- a/README.rst
++++ b/README.rst
+@@ -8,11 +8,13 @@ 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:
+
+ 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
+ ------------------
+@@ -57,12 +59,10 @@ Hacking
+ TODO
+ ----
+ * signs_ support (show warning and error icons to left of the buffer area)
+- * quickfix_ support (allow jumping forward and back through the error list)
+ * configuration variables
+ * parse or intercept useful output from the warnings module
+
+ .. _signs: http://www.vim.org/htmldoc/sign.html
+-.. _quickfix: http://vimdoc.sourceforge.net/htmldoc/quickfix.html
+
+ Changelog
+ ---------
+diff --git a/pyflakes.vim b/pyflakes.vim
+index 8aa508b..d6699bc 100644
+--- a/pyflakes.vim
++++ b/pyflakes.vim
+@@ -159,6 +159,42 @@ if !exists("*s:WideMsg")
+ endfun
+ endif
+
++if !exists("*s:GetQuickFixStackCount")
++ function s:GetQuickFixStackCount()
++ let l:stack_count = 0
++ try
++ silent colder 9
++ catch /E380:/
++ endtry
++
++ try
++ for i in range(9)
++ silent cnewer
++ let l:stack_count = l:stack_count + 1
++ endfor
++ catch /E381:/
++ return l:stack_count
++ endtry
++ endfunction
++endif
++
++if !exists("*s:ActivatePyflakesQuickFixWindow")
++ function s:ActivatePyflakesQuickFixWindow()
++ try
++ silent colder 9 " go to the bottom of quickfix stack
++ catch /E380:/
++ endtry
++
++ if s:pyflakes_qf > 0
++ try
++ exe "silent cnewer " . s:pyflakes_qf
++ catch /E381:/
++ echoerr "Could not activate Pyflakes Quickfix Window."
++ endtry
++ endif
++ endfunction
++endif
++
+ if !exists("*s:RunPyflakes")
+ function s:RunPyflakes()
+ highlight link PyFlakes SpellBad
+@@ -174,12 +210,23 @@ if !exists("*s:RunPyflakes")
+
+ let b:matched = []
+ let b:matchedlines = {}
++
++ let b:qf_list = []
++ let b:qf_window_count = -1
++
+ python << EOF
+ for w in check(vim.current.buffer):
+ vim.command('let s:matchDict = {}')
+ vim.command("let s:matchDict['lineNum'] = " + str(w.lineno))
+ vim.command("let s:matchDict['message'] = '%s'" % vim_quote(w.message % w.message_args))
+ vim.command("let b:matchedlines[" + str(w.lineno) + "] = s:matchDict")
++
++ vim.command("let l:qf_item = {}")
++ vim.command("let l:qf_item.bufnr = bufnr('%')")
++ vim.command("let l:qf_item.filename = expand('%')")
++ vim.command("let l:qf_item.lnum = %s" % str(w.lineno))
++ 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):
+ # without column information, just highlight the whole line
+@@ -189,8 +236,21 @@ for w in check(vim.current.buffer):
+ # with a column number, highlight the first keyword there
+ vim.command(r"let s:mID = matchadd('PyFlakes', '^\%" + str(w.lineno) + r"l\_.\{-}\zs\k\+\k\@!\%>" + str(w.col) + r"c')")
+
++ vim.command("let l:qf_item.vcol = 1")
++ vim.command("let l:qf_item.col = %s" % str(w.col + 1))
++
+ vim.command("call add(b:matched, s:matchDict)")
++ vim.command("call add(b:qf_list, l:qf_item)")
+ EOF
++ if exists("s:pyflakes_qf")
++ " if pyflakes quickfix window is already created, reuse it
++ call s:ActivatePyflakesQuickFixWindow()
++ call setqflist(b:qf_list, 'r')
++ else
++ " one pyflakes quickfix window for all buffer
++ call setqflist(b:qf_list, '')
++ let s:pyflakes_qf = s:GetQuickFixStackCount()
++ endif
+ let b:cleared = 0
+ endfunction
+ end
Please sign in to comment.
Something went wrong with that request. Please try again.