mirror of https://github.com/amix/vimrc.git
pull/45/head
parent
e62adb8084
commit
8265dca5d5
@ -0,0 +1 @@ |
||||
*.pyc |
@ -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. |
||||
|
||||
|
@ -0,0 +1,80 @@ |
||||
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, 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 |
||||
|
||||
.. _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. |
||||
|
@ -0,0 +1,325 @@ |
||||
" 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 !has('python') |
||||
" exit if python is not available. |
||||
finish |
||||
endif |
||||
|
||||
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 |
||||
|
||||
if !exists('g:pyflakes_use_quickfix') |
||||
let g:pyflakes_use_quickfix = 1 |
||||
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) |
||||
|
||||
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, loc(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 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') |
||||
if vimenc: |
||||
contents = contents.decode(vimenc) |
||||
|
||||
builtins = set(['__file__']) |
||||
try: |
||||
builtins.update(set(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 or '<unknown>') |
||||
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: |
||||
# 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 |
||||
|
||||
|
||||
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 strpart(a:msg, 0, &columns-1) |
||||
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 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\@!')") |
||||
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 g:pyflakes_use_quickfix == 1 |
||||
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 |
||||
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 |
||||
|
@ -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. |
@ -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 |
@ -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 |
||||
|
@ -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,0 +1,2 @@ |
||||
|
||||
__version__ = '0.4.0' |
@ -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 |
@ -0,0 +1,625 @@ |
||||
# -*- 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 |
||||
|
||||
|
||||
# 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): |
||||
""" |
||||
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): |
||||
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): |
||||
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 |
||||
|
||||
|
||||
|
||||
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 |
||||
|
||||
|
||||
# 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.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 deferFunction(self, callable): |
||||
''' |
||||
Schedule a function handler to be called just before completion. |
||||
|
||||
This is used for handling function bodies, which must be deferred |
||||
because code later in the file might modify the global scope. When |
||||
`callable` is called, the scope at the time this is called will be |
||||
restored, however it will contain any new bindings added to it. |
||||
''' |
||||
self._deferredFunctions.append((callable, self.scopeStack[:])) |
||||
|
||||
|
||||
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): |
||||
""" |
||||
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): |
||||
if not importation.used and importation.name not in all: |
||||
self.report( |
||||
messages.UnusedImport, |
||||
importation.source, |
||||
importation.name) |
||||
|
||||
|
||||
def pushFunctionScope(self): |
||||
self.scopeStack.append(FunctionScope()) |
||||
|
||||
def pushClassScope(self): |
||||
self.scopeStack.append(ClassScope()) |
||||
|
||||
def report(self, messageClass, *args, **kwargs): |
||||
self.messages.append(messageClass(self.filename, *args, **kwargs)) |
||||
|
||||
def handleChildren(self, tree): |
||||
for node in iter_child_nodes(tree): |
||||
self.handleNode(node, tree) |
||||
|
||||
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: |
||||
handler = getattr(self, nodeType) |
||||
handler(node) |
||||
finally: |
||||
self.nodeDepth -= 1 |
||||
if self.traceTree: |
||||
print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__ |
||||
|
||||
def ignore(self, node): |
||||
pass |
||||
|
||||
# "stmt" type nodes |
||||
RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \ |
||||
TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren |
||||
|
||||
CONTINUE = BREAK = PASS = ignore |
||||
|
||||
# "expr" type nodes |
||||
BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \ |
||||
CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren |
||||
|
||||
NUM = STR = ELLIPSIS = ignore |
||||
|
||||
# "slice" type nodes |
||||
SLICE = EXTSLICE = INDEX = handleChildren |
||||
|
||||
# expression contexts are node instances too, though being constants |
||||
LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore |
||||
|
||||
# same for operators |
||||
AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \ |
||||
BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ |
||||
EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore |
||||
|
||||
# additional node types |
||||
COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren |
||||
|
||||
def addBinding(self, loc, value, reportRedef=True): |
||||
'''Called when a binding is altered. |
||||
|
||||
- `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. |
||||
''' |
||||
if (isinstance(self.scope.get(value.name), FunctionDefinition) |
||||
and isinstance(value, FunctionDefinition)): |
||||
self.report(messages.RedefinedFunction, |
||||
loc, value.name, self.scope[value.name].source) |
||||
|
||||
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): |
||||
|
||||
self.report(messages.RedefinedWhileUnused, |
||||
loc, value.name, scope[value.name].source) |
||||
|
||||
if isinstance(value, UnBinding): |
||||
try: |
||||
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 enclosing function scopes |
||||
|
||||
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 |
||||
|
||||
# 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(self.scope, FunctionScope) and node.id not in self.scope: |
||||
# for each function or module scope above us |
||||
for scope in self.scopeStack[:-1]: |
||||
if not isinstance(scope, (FunctionScope, ModuleScope)): |
||||
continue |
||||
# if the name was defined in that scope, and the name has |
||||
# been accessed already in the current scope, and hasn't |
||||
# been declared global |
||||
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 |
||||
|
||||
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: |
||||
binding = Assignment(node.id, node) |
||||
if node.id in self.scope: |
||||
binding.used = self.scope[node.id].used |
||||