mirror of
https://github.com/gryf/.vim.git
synced 2025-12-17 19:40:29 +01:00
Added branch pathogen
This commit is contained in:
@@ -1,58 +0,0 @@
|
||||
setlocal cinkeys-=0#
|
||||
setlocal indentkeys-=0#
|
||||
setlocal foldlevel=100
|
||||
setlocal foldmethod=indent
|
||||
setlocal list
|
||||
setlocal noautoindent
|
||||
setlocal smartindent
|
||||
setlocal cinwords=if,elif,else,for,while,try,except,finally,def,class,with
|
||||
setlocal smarttab
|
||||
|
||||
set wildignore+=*.pyc
|
||||
|
||||
inoremap # X<BS>#
|
||||
|
||||
"set ofu=syntaxcomplete#Complete
|
||||
|
||||
"autocmd FileType python setlocal omnifunc=pysmell#Complete
|
||||
let python_highlight_all=1
|
||||
|
||||
"I don't want to have pyflakes errors in qfix, it interfering with Pep8/Pylint
|
||||
let g:pyflakes_use_quickfix = 0
|
||||
|
||||
"Load views for py files
|
||||
autocmd BufWinLeave *.py mkview
|
||||
autocmd BufWinEnter *.py silent loadview
|
||||
|
||||
compiler pylint
|
||||
|
||||
finish "end here. all below is just for the record.
|
||||
|
||||
" Pylint function, which can be optionally mapped to some keys. Currently
|
||||
" not used.
|
||||
if !exists('*<SID>runPyLint')
|
||||
function <SID>runPyLint()
|
||||
echohl Statement
|
||||
echo "Running pylint (ctrl-c to cancel) ..."
|
||||
echohl Normal
|
||||
:Pylint
|
||||
endfunction
|
||||
endif
|
||||
|
||||
if !exists('*<SID>PyLintBuf')
|
||||
function <SID>PyLintBuf()
|
||||
echohl Statement
|
||||
echo "Running pylint (ctrl-c to cancel) ..."
|
||||
echohl Normal
|
||||
let file = expand('%:p')
|
||||
let cmd = 'pylint --reports=n --output-format=text "' . file . '"'
|
||||
|
||||
if has('win32') || has('win64')
|
||||
let cmd = 'cmd /c "' . cmd . '"'
|
||||
endif
|
||||
|
||||
exec "bel silent new " . file . ".lint"
|
||||
exec "silent! read! " . cmd
|
||||
endfunction
|
||||
endif
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
" Fold routines for python code, version 3.2
|
||||
" Source: http://www.vim.org/scripts/script.php?script_id=2527
|
||||
" Last Change: 2009 Feb 25
|
||||
" Author: Jurjen Bos
|
||||
" Bug fixes and helpful comments: Grissiom, David Froger, Andrew McNabb
|
||||
|
||||
" Principles:
|
||||
" - a def/class starts a fold
|
||||
" a line with indent less than the previous def/class ends a fold
|
||||
" empty lines and comment lines are linked to the previous fold
|
||||
" comment lines outside a def/class are never folded
|
||||
" other lines outside a def/class are folded together as a group
|
||||
" for algorithm, see bottom of script
|
||||
|
||||
" - optionally, you can get empty lines between folds, see (***)
|
||||
" - another option is to ignore non-python files see (**)
|
||||
" - you can also modify the def/class check,
|
||||
" allowing for multiline def and class definitions see (*)
|
||||
|
||||
" Note for vim 7 users:
|
||||
" Vim 6 line numbers always take 8 columns, while vim 7 has a numberwidth variable
|
||||
" you can change the 8 below to &numberwidth if you have vim 7,
|
||||
" this is only really useful when you plan to use more than 8 columns (i.e. never)
|
||||
|
||||
" Note for masochists trying to read this:
|
||||
" I wanted to keep the functions short, so I replaced occurences of
|
||||
" if condition
|
||||
" statement
|
||||
" by
|
||||
" if condition | statement
|
||||
" wherever I found that useful
|
||||
|
||||
" (*)
|
||||
" class definitions are supposed to ontain a colon on the same line.
|
||||
" function definitions are *not* required to have a colon, to allow for multiline defs.
|
||||
" I you disagree, use instead of the pattern '^\s*\(class\s.*:\|def\s\)'
|
||||
" to enforce : for defs: '^\s*\(class\|def\)\s.*:'
|
||||
" you'll have to do this in two places.
|
||||
let s:defpat = '^\s*\(@\|class\s.*:\|def\s\)'
|
||||
|
||||
" (**) Ignore non-python files
|
||||
" Commented out because some python files are not recognized by Vim
|
||||
"if &filetype != 'python'
|
||||
" finish
|
||||
"endif
|
||||
|
||||
setlocal foldmethod=expr
|
||||
setlocal foldexpr=GetPythonFold(v:lnum)
|
||||
setlocal foldtext=PythonFoldText()
|
||||
|
||||
function! PythonFoldText()
|
||||
let fs = v:foldstart
|
||||
while getline(fs) =~ '^\s*@' | let fs = nextnonblank(fs + 1)
|
||||
endwhile
|
||||
let line = getline(fs)
|
||||
let nnum = nextnonblank(fs + 1)
|
||||
let nextline = getline(nnum)
|
||||
"get the document string: next line is ''' or """
|
||||
if nextline =~ "^\\s\\+[\"']\\{3}\\s*$"
|
||||
let line = line . " " . matchstr(getline(nextnonblank(nnum + 1)), '^\s*\zs.*\ze$')
|
||||
"next line starts with qoutes, and has text
|
||||
elseif nextline =~ "^\\s\\+[\"']\\{1,3}"
|
||||
let line = line." ".matchstr(nextline, "^\\s\\+[\"']\\{1,3}\\zs.\\{-}\\ze['\"]\\{0,3}$")
|
||||
elseif nextline =~ '^\s\+pass\s*$'
|
||||
let line = line . ' pass'
|
||||
endif
|
||||
"compute the width of the visible part of the window (see Note above)
|
||||
let w = winwidth(0) - &foldcolumn - (&number ? 8 : 0)
|
||||
let size = 1 + v:foldend - v:foldstart
|
||||
"compute expansion string
|
||||
let spcs = '................'
|
||||
while strlen(spcs) < w | let spcs = spcs . spcs
|
||||
endwhile
|
||||
"expand tabs (mail me if you have tabstop>10)
|
||||
let onetab = strpart(' ', 0, &tabstop)
|
||||
let line = substitute(line, '\t', onetab, 'g')
|
||||
return strpart(line.spcs, 0, w-strlen(size)-7).'.'.size.' lines'
|
||||
endfunction
|
||||
|
||||
function! GetBlockIndent(lnum)
|
||||
" Auxiliary function; determines the indent level of the surrounding def/class
|
||||
" "global" lines are level 0, first def &shiftwidth, and so on
|
||||
" scan backwards for class/def that is shallower or equal
|
||||
let ind = 100
|
||||
let p = a:lnum+1
|
||||
while indent(p) >= 0
|
||||
let p = p - 1
|
||||
" skip empty and comment lines
|
||||
if getline(p) =~ '^$\|^\s*#' | continue
|
||||
" zero-level regular line
|
||||
elseif indent(p) == 0 | return 0
|
||||
" skip deeper or equal lines
|
||||
elseif indent(p) >= ind || getline(p) =~ '^$\|^\s*#' | continue
|
||||
" indent is strictly less at this point: check for def/class
|
||||
elseif getline(p) =~ s:defpat && getline(p) !~ '^\s*@'
|
||||
" level is one more than this def/class
|
||||
return indent(p) + &shiftwidth
|
||||
endif
|
||||
" shallower line that is neither class nor def: continue search at new level
|
||||
let ind = indent(p)
|
||||
endwhile
|
||||
"beginning of file
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
" Clever debug code, use as: call PrintIfCount(n,"Line: ".a:lnum.", value: ".x)
|
||||
let s:counter=0
|
||||
function! PrintIfCount(n,t)
|
||||
"Print text the nth time this function is called
|
||||
let s:counter = s:counter+1
|
||||
if s:counter==a:n | echo a:t
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! GetPythonFold(lnum)
|
||||
" Determine folding level in Python source (see "higher foldlevel theory" below)
|
||||
let line = getline(a:lnum)
|
||||
let ind = indent(a:lnum)
|
||||
" Case D***: class and def start a fold
|
||||
" If previous line is @, it is not the first
|
||||
if line =~ s:defpat && getline(prevnonblank(a:lnum-1)) !~ '^\s*@'
|
||||
" let's see if this range of 0 or more @'s end in a class/def
|
||||
let n = a:lnum
|
||||
while getline(n) =~ '^\s*@' | let n = nextnonblank(n + 1)
|
||||
endwhile
|
||||
" yes, we have a match: this is the first of a real def/class with decorators
|
||||
if getline(n) =~ s:defpat
|
||||
return ">".(ind/&shiftwidth+1)
|
||||
endif
|
||||
" Case E***: empty lines fold with previous
|
||||
" (***) change '=' to -1 if you want empty lines/comment out of a fold
|
||||
elseif line == '' | return '='
|
||||
endif
|
||||
" now we need the indent from previous
|
||||
let p = prevnonblank(a:lnum-1)
|
||||
while p>0 && getline(p) =~ '^\s*#' | let p = prevnonblank(p-1)
|
||||
endwhile
|
||||
let pind = indent(p)
|
||||
" If previous was definition: count as one level deeper
|
||||
if getline(p) =~ s:defpat && getline(prevnonblank(a:lnum - 1)) !~ '^\s*@'
|
||||
let pind = pind + &shiftwidth
|
||||
" if begin of file: take zero
|
||||
elseif p==0 | let pind = 0
|
||||
endif
|
||||
" Case S*=* and C*=*: indent equal
|
||||
if ind>0 && ind==pind | return '='
|
||||
" Case S*>* and C*>*: indent increase
|
||||
elseif ind>pind | return '='
|
||||
" All cases with 0 indent
|
||||
elseif ind==0
|
||||
" Case C*=0*: separate global code blocks
|
||||
if pind==0 && line =~ '^#' | return 0
|
||||
" Case S*<0* and S*=0*: global code
|
||||
elseif line !~'^#'
|
||||
" Case S*<0*: new global statement if/while/for/try/with
|
||||
if 0<pind && line!~'^else\s*:\|^except.*:\|^elif.*:\|^finally\s*:' | return '>1'
|
||||
" Case S*=0*, after level 0 comment
|
||||
elseif 0==pind && getline(prevnonblank(a:lnum-1)) =~ '^\s*#' | return '>1'
|
||||
" Case S*=0*, other, stay 1
|
||||
else | return '='
|
||||
endif
|
||||
endif
|
||||
" Case C*<0= and C*<0<: compute next indent
|
||||
let n = nextnonblank(a:lnum+1)
|
||||
while n>0 && getline(n) =~'^\s*#' | let n = nextnonblank(n+1)
|
||||
endwhile
|
||||
" Case C*<0=: split definitions
|
||||
if indent(n)==0 | return 0
|
||||
" Case C*<0<: shallow comment
|
||||
else | return -1
|
||||
end
|
||||
endif
|
||||
" now we really need to compute the actual fold indent
|
||||
" do the hard computation
|
||||
let blockindent = GetBlockIndent(a:lnum)
|
||||
" Case SG<* and CG<*: global code, level 1
|
||||
if blockindent==0 | return 1
|
||||
endif
|
||||
" now we need the indent from next
|
||||
let n = nextnonblank(a:lnum+1)
|
||||
while n>0 && getline(n) =~'^\s*#' | let n = nextnonblank(n+1)
|
||||
endwhile
|
||||
let nind = indent(n)
|
||||
" Case CR<= and CR<>
|
||||
"if line !~ '^\s*#' | call PrintIfCount(4,"Line: ".a:lnum.", blockindent: ".blockindent.", n: ".n.", nind: ".nind.", p: ".p.", pind: ".pind)
|
||||
endif
|
||||
if line =~ '^\s*#' && ind>=nind | return -1
|
||||
" Case CR<<: return next indent
|
||||
elseif line =~ '^\s*#' | return nind / &shiftwidth
|
||||
" Case SR<*: return actual indent
|
||||
else | return blockindent / &shiftwidth
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" higher foldlevel theory
|
||||
" There are five kinds of statements: S (code), D (def/class), E (empty), C (comment)
|
||||
|
||||
" Note that a decorator statement (beginning with @) counts as definition,
|
||||
" but that of a sequence of @,@,@,def only the first one counts
|
||||
" This means that a definiion only counts if not preceded by a decorator
|
||||
|
||||
" There are two kinds of folds: R (regular), G (global statements)
|
||||
|
||||
" There are five indent situations with respect to the previous non-emtpy non-comment line:
|
||||
" > (indent), < (dedent), = (same); < and = combine with 0 (indent is zero)
|
||||
" Note: if the previous line is class/def, its indent is interpreted as one higher
|
||||
|
||||
" There are three indent situations with respect to the next (non-E non-C) line:
|
||||
" > (dedent), < (indent), = (same)
|
||||
|
||||
" Situations (in order of the script):
|
||||
" stat fold prev next
|
||||
" SDEC RG ><=00 ><=
|
||||
" D * * * begin fold level if previous is not @: '>'.ind/&sw+1
|
||||
" E * * * keep with previous: '='
|
||||
" S * = * stays the same: '='
|
||||
" C * = * combine with previous: '='
|
||||
" S * > * stays the same: '='
|
||||
" C * > * combine with previous: '='
|
||||
" C * =0 * separate blocks: 0
|
||||
" S * <0 * becomes new level 1: >1 (except except/else: 1)
|
||||
" S * =0 * stays 1: '=' (after level 0 comment: '>1')
|
||||
" C * <0 = split definitions: 0
|
||||
" C * <0 < shallow comment: -1
|
||||
" C * <0 > [never occurs]
|
||||
" S G < * global, not the first: 1
|
||||
" C G < * indent isn't 0: 1
|
||||
" C R < = foldlevel as computed for next line: -1
|
||||
" C R < > foldlevel as computed for next line: -1
|
||||
" S R < * compute foldlevel the hard way: use function
|
||||
" C R < < foldlevel as computed for this line: use function
|
||||
@@ -1,135 +0,0 @@
|
||||
" File: pep8_fn.vim
|
||||
" Author: Roman 'gryf' Dobosz (gryf73 at gmail.com)
|
||||
" Version: 1.0
|
||||
" Last Modified: 2010-09-12
|
||||
" Description: {{{
|
||||
"
|
||||
" Overview
|
||||
" --------
|
||||
" This plugin provides functionality to static checks for python files
|
||||
" regarding PEP8 guidance[1] as ":Pep8" command.
|
||||
"
|
||||
" This function does not use pep8[2] command line utility, but relies on pep8
|
||||
" module.
|
||||
"
|
||||
" This script uses python, therefore VIm should be compiled with python
|
||||
" support. You can check it by issuing ":version" command, and search for
|
||||
" "+python" inside features list.
|
||||
"
|
||||
" Couple of ideas was taken from pyflakes.vim[3] plugin.
|
||||
"
|
||||
" Installation
|
||||
" ------------
|
||||
" 1. Copy the pep8_fn.vim file to the $HOME/.vim/ftplugin/python or
|
||||
" $HOME/vimfiles/ftplugin/python or $VIM/vimfiles/ftplugin/python
|
||||
" directory. If python directory doesn't exists, it should be created.
|
||||
" Refer to the following Vim help topics for more information about Vim
|
||||
" plugins:
|
||||
" :help add-plugin
|
||||
" :help add-global-plugin
|
||||
" :help runtimepath
|
||||
" 2. It should be possible to import pep8 from python interpreter (it should
|
||||
" report no error):
|
||||
" >>> import pep8
|
||||
" >>>
|
||||
" If there are errors, install pep8 first. Simplest way to do it, is to
|
||||
" use easy_install[4] shell command as a root:
|
||||
" # easy_install pep8
|
||||
" 3. Restart Vim.
|
||||
" 4. You can now use the ":Pep8" which will examine current python buffer
|
||||
" and open quickfix buffer with errors if any.
|
||||
"
|
||||
" [1] http://www.python.org/dev/peps/pep-0008/
|
||||
" [2] http://pypi.python.org/pypi/pep8
|
||||
" [3] http://www.vim.org/scripts/script.php?script_id=2441
|
||||
" [4] http://pypi.python.org/pypi/setuptools
|
||||
"
|
||||
" }}}
|
||||
|
||||
|
||||
if exists("b:did_pep8_plugin")
|
||||
finish " only load once
|
||||
else
|
||||
let b:did_pep8_plugin = 1
|
||||
endif
|
||||
|
||||
if !exists("g:pep8_exclude")
|
||||
let g:pep8_exclude = []
|
||||
endif
|
||||
|
||||
if !exists("b:did_pep8_init")
|
||||
let b:did_pep8_init = 0
|
||||
|
||||
if !has('python')
|
||||
echoerr "pep8_fn.vim plugin requires Vim to be compiled with +python"
|
||||
finish
|
||||
endif
|
||||
|
||||
python << EOF
|
||||
import vim
|
||||
import sys
|
||||
from StringIO import StringIO
|
||||
|
||||
try:
|
||||
import pep8
|
||||
except ImportError:
|
||||
raise AssertionError('Error: pep8_fn.vim requires module pep8')
|
||||
|
||||
class VImPep8(object):
|
||||
|
||||
def __init__(self):
|
||||
self.fname = vim.current.buffer.name
|
||||
self.bufnr = vim.current.buffer.number
|
||||
self.output = []
|
||||
self.exclude_list = vim.eval("g:pep8_exclude")
|
||||
|
||||
def reporter(self, lnum, col, text, check):
|
||||
self.output.append([lnum, col, text])
|
||||
|
||||
def run(self):
|
||||
pep8.process_options(['-r', vim.current.buffer.name])
|
||||
checker = pep8.Checker(vim.current.buffer.name)
|
||||
checker.report_error = self.reporter
|
||||
checker.check_all()
|
||||
self.process_output()
|
||||
|
||||
def process_output(self):
|
||||
vim.command('call setqflist([])')
|
||||
qf_list = []
|
||||
qf_dict = {}
|
||||
|
||||
for line in self.output:
|
||||
skip = False
|
||||
for exclude_pattern in self.exclude_list:
|
||||
if exclude_pattern in line[2]:
|
||||
skip = True
|
||||
break
|
||||
if skip:
|
||||
continue
|
||||
qf_dict['bufnr'] = self.bufnr
|
||||
qf_dict['lnum'] = line[0]
|
||||
qf_dict['col'] = line[1]
|
||||
qf_dict['text'] = line[2]
|
||||
qf_dict['type'] = line[2][0]
|
||||
qf_list.append(qf_dict)
|
||||
qf_dict = {}
|
||||
|
||||
self.output = []
|
||||
vim.command('call setqflist(%s)' % str(qf_list))
|
||||
if qf_list:
|
||||
vim.command('copen')
|
||||
EOF
|
||||
let b:did_pep8_init = 1
|
||||
endif
|
||||
|
||||
if !exists('*s:Pep8')
|
||||
function s:Pep8()
|
||||
python << EOF
|
||||
VImPep8().run()
|
||||
EOF
|
||||
endfunction
|
||||
endif
|
||||
|
||||
if !exists(":Pep8")
|
||||
command Pep8 call s:Pep8()
|
||||
endif
|
||||
@@ -1,111 +0,0 @@
|
||||
"pydoc.vim: pydoc integration for vim
|
||||
"performs searches and can display the documentation of python modules
|
||||
"Author: Andr<64> Kelpe <efeshundertelf at googlemail dot com>
|
||||
"Author: Romain Chossart <romainchossat at gmail dot com>
|
||||
"Author: Matthias Vogelgesang
|
||||
"http://www.vim.org/scripts/script.php?script_id=910
|
||||
"This plugin integrates the pydoc into vim. You can view the
|
||||
"documentation of a module by using :Pydoc foo.bar.baz or search
|
||||
"a word (uses pydoc -k) in the documentation by typing PydocSearch
|
||||
"foobar. You can also view the documentation of the word under the
|
||||
"cursor by pressing <leader>pw or the WORD (see :help WORD) by pressing
|
||||
"<leader>pW. "This is very useful if you want to jump to a module which was found by
|
||||
"PydocSearch. To have a browser like feeling you can use u and CTRL-R to
|
||||
"go back and forward, just like editing normal text.
|
||||
|
||||
"If you want to use the script and pydoc is not in your PATH, just put a
|
||||
"line like
|
||||
|
||||
" let g:pydoc_cmd = \"/usr/bin/pydoc" (without the backslash!!)
|
||||
|
||||
"in your .vimrc
|
||||
|
||||
|
||||
"pydoc.vim is free software, you can redistribute or modify
|
||||
"it under the terms of the GNU General Public License Version 2 or any
|
||||
"later Version (see http://www.gnu.org/copyleft/gpl.html for details).
|
||||
|
||||
"Please feel free to contact me.
|
||||
|
||||
|
||||
set switchbuf=useopen
|
||||
function! ShowPyDoc(name, type)
|
||||
if !exists('g:pydoc_cmd')
|
||||
let g:pydoc_cmd = 'pydoc'
|
||||
endif
|
||||
|
||||
if bufloaded("__doc__") >0
|
||||
let l:buf_is_new = 0
|
||||
else
|
||||
let l:buf_is_new = 1
|
||||
endif
|
||||
|
||||
if bufnr("__doc__") >0
|
||||
execute "sb __doc__"
|
||||
else
|
||||
execute 'split __doc__'
|
||||
endif
|
||||
setlocal noswapfile
|
||||
set buftype=nofile
|
||||
setlocal modifiable
|
||||
normal ggdG
|
||||
" remove function/method arguments
|
||||
let s:name2 = substitute(a:name, '(.*', '', 'g' )
|
||||
" remove all colons
|
||||
let s:name2 = substitute(s:name2, ':', '', 'g' )
|
||||
if a:type==1
|
||||
execute "silent read ! " . g:pydoc_cmd . " " . s:name2
|
||||
else
|
||||
execute "silent read ! " . g:pydoc_cmd . " -k " . s:name2
|
||||
endif
|
||||
setlocal nomodified
|
||||
set filetype=man
|
||||
normal 1G
|
||||
|
||||
if !exists('g:pydoc_wh')
|
||||
let g:pydoc_wh = 10
|
||||
end
|
||||
resize -999
|
||||
execute "silent resize +" . g:pydoc_wh
|
||||
|
||||
if !exists('g:pydoc_highlight')
|
||||
let g:pydoc_highlight = 1
|
||||
endif
|
||||
if g:pydoc_highlight == 1
|
||||
call Highlight(s:name2)
|
||||
endif
|
||||
|
||||
let l:line = getline(2)
|
||||
if l:line =~ "^no Python documentation found for.*$"
|
||||
if l:buf_is_new
|
||||
execute "bd!"
|
||||
else
|
||||
normal u
|
||||
endif
|
||||
redraw
|
||||
echohl WarningMsg | echo l:line | echohl None
|
||||
endif
|
||||
endfunction
|
||||
|
||||
"highlighting
|
||||
function! Highlight(name)
|
||||
execute "sb __doc__"
|
||||
set filetype=man
|
||||
"syn on
|
||||
execute 'syntax keyword pydoc '.a:name
|
||||
hi pydoc gui=reverse
|
||||
endfunction
|
||||
|
||||
"mappings
|
||||
au FileType python,man map <buffer> <leader>pw :call ShowPyDoc('<C-R><C-W>', 1)<CR>
|
||||
au FileType python,man map <buffer> <leader>pW :call ShowPyDoc('<C-R><C-A>', 1)<CR>
|
||||
au FileType python,man map <buffer> <leader>pk :call ShowPyDoc('<C-R><C-W>', 0)<CR>
|
||||
au FileType python,man map <buffer> <leader>pK :call ShowPyDoc('<C-R><C-A>', 0)<CR>
|
||||
|
||||
" remap the K (or 'help') key
|
||||
nnoremap <silent> <buffer> K :call ShowPyDoc(expand("<cword>"), 1)<CR>
|
||||
|
||||
|
||||
"commands
|
||||
command! -nargs=1 Pydoc :call ShowPyDoc('<args>', 1)
|
||||
command! -nargs=* PydocSearch :call ShowPyDoc('<args>', 0)
|
||||
@@ -1,321 +0,0 @@
|
||||
" 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
|
||||
|
||||
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
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
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.
|
||||
@@ -1,29 +0,0 @@
|
||||
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
|
||||
@@ -1,82 +0,0 @@
|
||||
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. If you're using pathogen_, unzip the contents of ``pyflakes-vim.zip`` into
|
||||
its own bundle directory, i.e. into ``~/.vim/bundle/pyflakes-vim/``.
|
||||
|
||||
Otherwise 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
|
||||
.. _pathogen: http://www.vim.org/scripts/script.php?script_id=2332
|
||||
.. _runtime path: http://vimdoc.sourceforge.net/htmldoc/options.html#'runtimepath'
|
||||
|
||||
Running from source
|
||||
-------------------
|
||||
|
||||
If you're running pyflakes-vim "from source," you'll need the PyFlakes library
|
||||
on your PYTHONPATH somewhere. (It is included in the vim.org zipfile.) I recommend
|
||||
getting my PyFlakes_ fork, which retains column number information, giving 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.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
from pyflakes.scripts.pyflakes import main
|
||||
main()
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
__version__ = '0.4.0'
|
||||
@@ -1,625 +0,0 @@
|
||||
# -*- 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
|
||||
self.addBinding(node, binding)
|
||||
elif isinstance(node.ctx, _ast.Del):
|
||||
if isinstance(self.scope, FunctionScope) and \
|
||||
node.id in self.scope.globals:
|
||||
del self.scope.globals[node.id]
|
||||
else:
|
||||
self.addBinding(node, UnBinding(node.id, node))
|
||||
else:
|
||||
# must be a Param context -- this only happens for names in function
|
||||
# arguments, but these aren't dispatched through here
|
||||
raise RuntimeError(
|
||||
"Got impossible expression context: %r" % (node.ctx,))
|
||||
|
||||
|
||||
def FUNCTIONDEF(self, node):
|
||||
# the decorators attribute is called decorator_list as of Python 2.6
|
||||
if hasattr(node, 'decorators'):
|
||||
for deco in node.decorators:
|
||||
self.handleNode(deco, node)
|
||||
else:
|
||||
for deco in node.decorator_list:
|
||||
self.handleNode(deco, node)
|
||||
self.addBinding(node, FunctionDefinition(node.name, node))
|
||||
self.LAMBDA(node)
|
||||
|
||||
def LAMBDA(self, node):
|
||||
for default in node.args.defaults:
|
||||
self.handleNode(default, node)
|
||||
|
||||
def runFunction():
|
||||
args = []
|
||||
|
||||
def addArgs(arglist):
|
||||
for arg in arglist:
|
||||
if isinstance(arg, _ast.Tuple):
|
||||
addArgs(arg.elts)
|
||||
else:
|
||||
if arg.id in args:
|
||||
self.report(messages.DuplicateArgument,
|
||||
node, arg.id)
|
||||
args.append(arg.id)
|
||||
|
||||
self.pushFunctionScope()
|
||||
addArgs(node.args.args)
|
||||
# vararg/kwarg identifiers are not Name nodes
|
||||
if node.args.vararg:
|
||||
args.append(node.args.vararg)
|
||||
if node.args.kwarg:
|
||||
args.append(node.args.kwarg)
|
||||
for name in args:
|
||||
self.addBinding(node, Argument(name, node), reportRedef=False)
|
||||
if isinstance(node.body, list):
|
||||
# case for FunctionDefs
|
||||
for stmt in node.body:
|
||||
self.handleNode(stmt, node)
|
||||
else:
|
||||
# case for Lambdas
|
||||
self.handleNode(node.body, node)
|
||||
def checkUnusedAssignments():
|
||||
"""
|
||||
Check to see if any assignments have not been used.
|
||||
"""
|
||||
for name, binding in self.scope.iteritems():
|
||||
if (not binding.used and not name in self.scope.globals
|
||||
and isinstance(binding, Assignment)):
|
||||
self.report(messages.UnusedVariable,
|
||||
binding.source, name)
|
||||
self.deferAssignment(checkUnusedAssignments)
|
||||
self.popScope()
|
||||
|
||||
self.deferFunction(runFunction)
|
||||
|
||||
|
||||
def CLASSDEF(self, node):
|
||||
"""
|
||||
Check names used in a class definition, including its decorators, base
|
||||
classes, and the body of its definition. Additionally, add its name to
|
||||
the current scope.
|
||||
"""
|
||||
# decorator_list is present as of Python 2.6
|
||||
for deco in getattr(node, 'decorator_list', []):
|
||||
self.handleNode(deco, node)
|
||||
for baseNode in node.bases:
|
||||
self.handleNode(baseNode, node)
|
||||
self.pushClassScope()
|
||||
for stmt in node.body:
|
||||
self.handleNode(stmt, node)
|
||||
self.popScope()
|
||||
self.addBinding(node, Binding(node.name, node))
|
||||
|
||||
def ASSIGN(self, node):
|
||||
self.handleNode(node.value, node)
|
||||
for target in node.targets:
|
||||
self.handleNode(target, node)
|
||||
|
||||
def AUGASSIGN(self, node):
|
||||
# AugAssign is awkward: must set the context explicitly and visit twice,
|
||||
# once with AugLoad context, once with AugStore context
|
||||
node.target.ctx = _ast.AugLoad()
|
||||
self.handleNode(node.target, node)
|
||||
self.handleNode(node.value, node)
|
||||
node.target.ctx = _ast.AugStore()
|
||||
self.handleNode(node.target, node)
|
||||
|
||||
def IMPORT(self, node):
|
||||
for alias in node.names:
|
||||
name = alias.asname or alias.name
|
||||
importation = Importation(name, node)
|
||||
self.addBinding(node, importation)
|
||||
|
||||
def IMPORTFROM(self, node):
|
||||
if node.module == '__future__':
|
||||
if not self.futuresAllowed:
|
||||
self.report(messages.LateFutureImport, node,
|
||||
[n.name for n in node.names])
|
||||
else:
|
||||
self.futuresAllowed = False
|
||||
|
||||
for alias in node.names:
|
||||
if alias.name == '*':
|
||||
self.scope.importStarred = True
|
||||
self.report(messages.ImportStarUsed, node, node.module)
|
||||
continue
|
||||
name = alias.asname or alias.name
|
||||
importation = Importation(name, node)
|
||||
if node.module == '__future__':
|
||||
importation.used = (self.scope, node)
|
||||
self.addBinding(node, importation)
|
||||
@@ -1,96 +0,0 @@
|
||||
# (c) 2005 Divmod, Inc. See LICENSE file for details
|
||||
|
||||
class Message(object):
|
||||
message = ''
|
||||
message_args = ()
|
||||
def __init__(self, filename, loc, use_column=True):
|
||||
self.filename = filename
|
||||
self.lineno = loc.lineno
|
||||
self.col = getattr(loc, 'col_offset', None) if use_column else None
|
||||
|
||||
def __str__(self):
|
||||
return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args)
|
||||
|
||||
|
||||
class UnusedImport(Message):
|
||||
message = '%r imported but unused'
|
||||
def __init__(self, filename, loc, name):
|
||||
Message.__init__(self, filename, loc, use_column=False)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
class RedefinedWhileUnused(Message):
|
||||
message = 'redefinition of unused %r from line %r'
|
||||
def __init__(self, filename, loc, name, orig_loc):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name, orig_loc.lineno)
|
||||
|
||||
|
||||
class ImportShadowedByLoopVar(Message):
|
||||
message = 'import %r from line %r shadowed by loop variable'
|
||||
def __init__(self, filename, loc, name, orig_loc):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name, orig_loc.lineno)
|
||||
|
||||
|
||||
class ImportStarUsed(Message):
|
||||
message = "'from %s import *' used; unable to detect undefined names"
|
||||
def __init__(self, filename, loc, modname):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (modname,)
|
||||
|
||||
|
||||
class UndefinedName(Message):
|
||||
message = 'undefined name %r'
|
||||
def __init__(self, filename, loc, name):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
|
||||
class UndefinedExport(Message):
|
||||
message = 'undefined name %r in __all__'
|
||||
def __init__(self, filename, loc, name):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
|
||||
class UndefinedLocal(Message):
|
||||
message = "local variable %r (defined in enclosing scope on line %r) referenced before assignment"
|
||||
def __init__(self, filename, loc, name, orig_loc):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name, orig_loc.lineno)
|
||||
|
||||
|
||||
class DuplicateArgument(Message):
|
||||
message = 'duplicate argument %r in function definition'
|
||||
def __init__(self, filename, loc, name):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
class RedefinedFunction(Message):
|
||||
message = 'redefinition of function %r from line %r'
|
||||
def __init__(self, filename, loc, name, orig_loc):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (name, orig_loc.lineno)
|
||||
|
||||
|
||||
class LateFutureImport(Message):
|
||||
message = 'future import(s) %r after other statements'
|
||||
def __init__(self, filename, loc, names):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (names,)
|
||||
|
||||
|
||||
class UnusedVariable(Message):
|
||||
"""
|
||||
Indicates that a variable has been explicity assigned to but not actually
|
||||
used.
|
||||
"""
|
||||
|
||||
message = 'local variable %r is assigned to but never used'
|
||||
def __init__(self, filename, loc, names):
|
||||
Message.__init__(self, filename, loc)
|
||||
self.message_args = (names,)
|
||||
@@ -1,90 +0,0 @@
|
||||
|
||||
"""
|
||||
Implementation of the command-line I{pyflakes} tool.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import _ast
|
||||
|
||||
checker = __import__('pyflakes.checker').checker
|
||||
|
||||
def check(codeString, filename):
|
||||
"""
|
||||
Check the Python source given by C{codeString} for flakes.
|
||||
|
||||
@param codeString: The Python source to check.
|
||||
@type codeString: C{str}
|
||||
|
||||
@param filename: The name of the file the source came from, used to report
|
||||
errors.
|
||||
@type filename: C{str}
|
||||
|
||||
@return: The number of warnings emitted.
|
||||
@rtype: C{int}
|
||||
"""
|
||||
# First, compile into an AST and handle syntax errors.
|
||||
try:
|
||||
tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
|
||||
except SyntaxError, value:
|
||||
msg = value.args[0]
|
||||
|
||||
(lineno, offset, text) = value.lineno, value.offset, value.text
|
||||
|
||||
# If there's an encoding problem with the file, the text is None.
|
||||
if text is None:
|
||||
# Avoid using msg, since for the only known case, it contains a
|
||||
# bogus message that claims the encoding the file declared was
|
||||
# unknown.
|
||||
print >> sys.stderr, "%s: problem decoding source" % (filename, )
|
||||
else:
|
||||
line = text.splitlines()[-1]
|
||||
|
||||
if offset is not None:
|
||||
offset = offset - (len(text) - len(line))
|
||||
|
||||
print >> sys.stderr, '%s:%d: %s' % (filename, lineno, msg)
|
||||
print >> sys.stderr, line
|
||||
|
||||
if offset is not None:
|
||||
print >> sys.stderr, " " * offset, "^"
|
||||
|
||||
return 1
|
||||
else:
|
||||
# Okay, it's syntactically valid. Now check it.
|
||||
w = checker.Checker(tree, filename)
|
||||
w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
|
||||
for warning in w.messages:
|
||||
print warning
|
||||
return len(w.messages)
|
||||
|
||||
|
||||
def checkPath(filename):
|
||||
"""
|
||||
Check the given path, printing out any warnings detected.
|
||||
|
||||
@return: the number of warnings printed
|
||||
"""
|
||||
try:
|
||||
return check(file(filename, 'U').read() + '\n', filename)
|
||||
except IOError, msg:
|
||||
print >> sys.stderr, "%s: %s" % (filename, msg.args[1])
|
||||
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)
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
import textwrap
|
||||
import _ast
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
from pyflakes import checker
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def flakes(self, input, *expectedOutputs, **kw):
|
||||
ast = compile(textwrap.dedent(input), "<test>", "exec",
|
||||
_ast.PyCF_ONLY_AST)
|
||||
w = checker.Checker(ast, **kw)
|
||||
outputs = [type(o) for o in w.messages]
|
||||
expectedOutputs = list(expectedOutputs)
|
||||
outputs.sort()
|
||||
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
|
||||
@@ -1,673 +0,0 @@
|
||||
|
||||
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_redefinedBySubclass(self):
|
||||
"""
|
||||
If an imported name is redefined by a class statement which also uses
|
||||
that name in the bases list, no warning is emitted.
|
||||
"""
|
||||
self.flakes('''
|
||||
from fu import bar
|
||||
class bar(bar):
|
||||
pass
|
||||
''')
|
||||
|
||||
|
||||
def test_redefinedInClass(self):
|
||||
"""
|
||||
Test that shadowing a global with a class attribute does not produce a
|
||||
warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
import fu
|
||||
class bar:
|
||||
fu = 1
|
||||
print fu
|
||||
''')
|
||||
|
||||
def test_usedInFunction(self):
|
||||
self.flakes('''
|
||||
import fu
|
||||
def fun():
|
||||
print fu
|
||||
''')
|
||||
|
||||
def test_shadowedByParameter(self):
|
||||
self.flakes('''
|
||||
import fu
|
||||
def fun(fu):
|
||||
print fu
|
||||
''', m.UnusedImport)
|
||||
|
||||
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):
|
||||
"""
|
||||
If a dotted name is imported and used, no warning is reported.
|
||||
"""
|
||||
self.flakes('''
|
||||
import fu.bar
|
||||
fu.bar
|
||||
''')
|
||||
|
||||
|
||||
def test_unusedPackageImport(self):
|
||||
"""
|
||||
If a dotted name is imported and not used, an unused import warning is
|
||||
reported.
|
||||
"""
|
||||
self.flakes('import fu.bar', m.UnusedImport)
|
||||
|
||||
|
||||
def test_duplicateSubmoduleImport(self):
|
||||
"""
|
||||
If a submodule of a package is imported twice, an unused import warning
|
||||
and a redefined while unused warning are reported.
|
||||
"""
|
||||
self.flakes('''
|
||||
import fu.bar, fu.bar
|
||||
fu.bar
|
||||
''', m.RedefinedWhileUnused)
|
||||
self.flakes('''
|
||||
import fu.bar
|
||||
import fu.bar
|
||||
fu.bar
|
||||
''', m.RedefinedWhileUnused)
|
||||
|
||||
|
||||
def test_differentSubmoduleImport(self):
|
||||
"""
|
||||
If two different submodules of a package are imported, no duplicate
|
||||
import warning is reported for the package.
|
||||
"""
|
||||
self.flakes('''
|
||||
import fu.bar, fu.baz
|
||||
fu.bar, fu.baz
|
||||
''')
|
||||
self.flakes('''
|
||||
import fu.bar
|
||||
import fu.baz
|
||||
fu.bar, fu.baz
|
||||
''')
|
||||
|
||||
def test_assignRHSFirst(self):
|
||||
self.flakes('import fu; fu = fu')
|
||||
self.flakes('import fu; fu, bar = fu')
|
||||
self.flakes('import fu; [fu, bar] = fu')
|
||||
self.flakes('import fu; fu += fu')
|
||||
|
||||
def test_tryingMultipleImports(self):
|
||||
self.flakes('''
|
||||
try:
|
||||
import fu
|
||||
except ImportError:
|
||||
import bar as fu
|
||||
''')
|
||||
test_tryingMultipleImports.todo = ''
|
||||
|
||||
def test_nonGlobalDoesNotRedefine(self):
|
||||
self.flakes('''
|
||||
import fu
|
||||
def a():
|
||||
fu = 3
|
||||
return fu
|
||||
fu
|
||||
''')
|
||||
|
||||
def test_functionsRunLater(self):
|
||||
self.flakes('''
|
||||
def a():
|
||||
fu
|
||||
import fu
|
||||
''')
|
||||
|
||||
def test_functionNamesAreBoundNow(self):
|
||||
self.flakes('''
|
||||
import fu
|
||||
def fu():
|
||||
fu
|
||||
fu
|
||||
''', m.RedefinedWhileUnused)
|
||||
|
||||
def test_ignoreNonImportRedefinitions(self):
|
||||
self.flakes('a = 1; a = 2')
|
||||
|
||||
def test_importingForImportError(self):
|
||||
self.flakes('''
|
||||
try:
|
||||
import fu
|
||||
except ImportError:
|
||||
pass
|
||||
''')
|
||||
test_importingForImportError.todo = ''
|
||||
|
||||
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')
|
||||
self.flakes('''
|
||||
"docstring is allowed before future import"
|
||||
from __future__ import division
|
||||
''')
|
||||
|
||||
def test_futureImportFirst(self):
|
||||
"""
|
||||
__future__ imports must come before anything else.
|
||||
"""
|
||||
self.flakes('''
|
||||
x = 5
|
||||
from __future__ import division
|
||||
''', m.LateFutureImport)
|
||||
self.flakes('''
|
||||
from foo import bar
|
||||
from __future__ import division
|
||||
bar
|
||||
''', m.LateFutureImport)
|
||||
|
||||
|
||||
|
||||
class TestSpecialAll(harness.Test):
|
||||
"""
|
||||
Tests for suppression of unused import warnings by C{__all__}.
|
||||
"""
|
||||
def test_ignoredInFunction(self):
|
||||
"""
|
||||
An C{__all__} definition does not suppress unused import warnings in a
|
||||
function scope.
|
||||
"""
|
||||
self.flakes('''
|
||||
def foo():
|
||||
import bar
|
||||
__all__ = ["bar"]
|
||||
''', m.UnusedImport, m.UnusedVariable)
|
||||
|
||||
|
||||
def test_ignoredInClass(self):
|
||||
"""
|
||||
An C{__all__} definition does not suppress unused import warnings in a
|
||||
class scope.
|
||||
"""
|
||||
self.flakes('''
|
||||
class foo:
|
||||
import bar
|
||||
__all__ = ["bar"]
|
||||
''', m.UnusedImport)
|
||||
|
||||
|
||||
def test_warningSuppressed(self):
|
||||
"""
|
||||
If a name is imported and unused but is named in C{__all__}, no warning
|
||||
is reported.
|
||||
"""
|
||||
self.flakes('''
|
||||
import foo
|
||||
__all__ = ["foo"]
|
||||
''')
|
||||
|
||||
|
||||
def test_unrecognizable(self):
|
||||
"""
|
||||
If C{__all__} is defined in a way that can't be recognized statically,
|
||||
it is ignored.
|
||||
"""
|
||||
self.flakes('''
|
||||
import foo
|
||||
__all__ = ["f" + "oo"]
|
||||
''', m.UnusedImport)
|
||||
self.flakes('''
|
||||
import foo
|
||||
__all__ = [] + ["foo"]
|
||||
''', m.UnusedImport)
|
||||
|
||||
|
||||
def test_unboundExported(self):
|
||||
"""
|
||||
If C{__all__} includes a name which is not bound, a warning is emitted.
|
||||
"""
|
||||
self.flakes('''
|
||||
__all__ = ["foo"]
|
||||
''', m.UndefinedExport)
|
||||
|
||||
# Skip this in __init__.py though, since the rules there are a little
|
||||
# different.
|
||||
for filename in ["foo/__init__.py", "__init__.py"]:
|
||||
self.flakes('''
|
||||
__all__ = ["foo"]
|
||||
''', filename=filename)
|
||||
|
||||
|
||||
def test_usedInGenExp(self):
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
class Python26Tests(harness.Test):
|
||||
"""
|
||||
Tests for checking of syntax which is valid in PYthon 2.6 and newer.
|
||||
"""
|
||||
if version_info < (2, 6):
|
||||
skip = "Python 2.6 required for class decorator tests."
|
||||
|
||||
|
||||
def test_usedAsClassDecorator(self):
|
||||
"""
|
||||
Using an imported name as a class decorator results in no warnings,
|
||||
but using an undefined name as a class decorator results in an
|
||||
undefined name warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
from interior import decorate
|
||||
@decorate
|
||||
class foo:
|
||||
pass
|
||||
''')
|
||||
|
||||
self.flakes('''
|
||||
from interior import decorate
|
||||
@decorate("foo")
|
||||
class bar:
|
||||
pass
|
||||
''')
|
||||
|
||||
self.flakes('''
|
||||
@decorate
|
||||
class foo:
|
||||
pass
|
||||
''', m.UndefinedName)
|
||||
@@ -1,575 +0,0 @@
|
||||
# (c) 2005-2010 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')
|
||||
|
||||
|
||||
def test_undefinedBaseClass(self):
|
||||
"""
|
||||
If a name in the base list of a class definition is undefined, a
|
||||
warning is emitted.
|
||||
"""
|
||||
self.flakes('''
|
||||
class foo(foo):
|
||||
pass
|
||||
''', m.UndefinedName)
|
||||
|
||||
|
||||
def test_classNameUndefinedInClassBody(self):
|
||||
"""
|
||||
If a class name is used in the body of that class's definition and
|
||||
the name is not already defined, a warning is emitted.
|
||||
"""
|
||||
self.flakes('''
|
||||
class foo:
|
||||
foo
|
||||
''', m.UndefinedName)
|
||||
|
||||
|
||||
def test_classNameDefinedPreviously(self):
|
||||
"""
|
||||
If a class name is used in the body of that class's definition and
|
||||
the name was previously defined in some other way, no warning is
|
||||
emitted.
|
||||
"""
|
||||
self.flakes('''
|
||||
foo = None
|
||||
class foo:
|
||||
foo
|
||||
''')
|
||||
|
||||
|
||||
def test_comparison(self):
|
||||
"""
|
||||
If a defined name is used on either side of any of the six comparison
|
||||
operators, no warning is emitted.
|
||||
"""
|
||||
self.flakes('''
|
||||
x = 10
|
||||
y = 20
|
||||
x < y
|
||||
x <= y
|
||||
x == y
|
||||
x != y
|
||||
x >= y
|
||||
x > y
|
||||
''')
|
||||
|
||||
|
||||
def test_identity(self):
|
||||
"""
|
||||
If a deefined name is used on either side of an identity test, no
|
||||
warning is emitted.
|
||||
"""
|
||||
self.flakes('''
|
||||
x = 10
|
||||
y = 20
|
||||
x is y
|
||||
x is not y
|
||||
''')
|
||||
|
||||
|
||||
def test_containment(self):
|
||||
"""
|
||||
If a defined name is used on either side of a containment test, no
|
||||
warning is emitted.
|
||||
"""
|
||||
self.flakes('''
|
||||
x = 10
|
||||
y = 20
|
||||
x in y
|
||||
x not in y
|
||||
''')
|
||||
|
||||
|
||||
def test_loopControl(self):
|
||||
"""
|
||||
break and continue statements are supported.
|
||||
"""
|
||||
self.flakes('''
|
||||
for x in [1, 2]:
|
||||
break
|
||||
''')
|
||||
self.flakes('''
|
||||
for x in [1, 2]:
|
||||
continue
|
||||
''')
|
||||
|
||||
|
||||
def test_ellipsis(self):
|
||||
"""
|
||||
Ellipsis in a slice is supported.
|
||||
"""
|
||||
self.flakes('''
|
||||
[1, 2][...]
|
||||
''')
|
||||
|
||||
|
||||
def test_extendedSlice(self):
|
||||
"""
|
||||
Extended slices are supported.
|
||||
"""
|
||||
self.flakes('''
|
||||
x = 3
|
||||
[1, 2][x,:]
|
||||
''')
|
||||
|
||||
|
||||
|
||||
class TestUnusedAssignment(harness.Test):
|
||||
"""
|
||||
Tests for warning about unused assignments.
|
||||
"""
|
||||
|
||||
def test_unusedVariable(self):
|
||||
"""
|
||||
Warn when a variable in a function is assigned a value that's never
|
||||
used.
|
||||
"""
|
||||
self.flakes('''
|
||||
def a():
|
||||
b = 1
|
||||
''', m.UnusedVariable)
|
||||
|
||||
|
||||
def test_assignToGlobal(self):
|
||||
"""
|
||||
Assigning to a global and then not using that global is perfectly
|
||||
acceptable. Do not mistake it for an unused local variable.
|
||||
"""
|
||||
self.flakes('''
|
||||
b = 0
|
||||
def a():
|
||||
global b
|
||||
b = 1
|
||||
''')
|
||||
|
||||
|
||||
def test_assignToMember(self):
|
||||
"""
|
||||
Assigning to a member of another object and then not using that member
|
||||
variable is perfectly acceptable. Do not mistake it for an unused
|
||||
local variable.
|
||||
"""
|
||||
# XXX: Adding this test didn't generate a failure. Maybe not
|
||||
# necessary?
|
||||
self.flakes('''
|
||||
class b:
|
||||
pass
|
||||
def a():
|
||||
b.foo = 1
|
||||
''')
|
||||
|
||||
|
||||
def test_assignInForLoop(self):
|
||||
"""
|
||||
Don't warn when a variable in a for loop is assigned to but not used.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
for i in range(10):
|
||||
pass
|
||||
''')
|
||||
|
||||
|
||||
def test_assignInListComprehension(self):
|
||||
"""
|
||||
Don't warn when a variable in a list comprehension is assigned to but
|
||||
not used.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
[None for i in range(10)]
|
||||
''')
|
||||
|
||||
|
||||
def test_generatorExpression(self):
|
||||
"""
|
||||
Don't warn when a variable in a generator expression is assigned to but not used.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
(None for i in range(10))
|
||||
''')
|
||||
|
||||
|
||||
def test_assignmentInsideLoop(self):
|
||||
"""
|
||||
Don't warn when a variable assignment occurs lexically after its use.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
x = None
|
||||
for i in range(10):
|
||||
if i > 2:
|
||||
return x
|
||||
x = i * 2
|
||||
''')
|
||||
|
||||
|
||||
def test_tupleUnpacking(self):
|
||||
"""
|
||||
Don't warn when a variable included in tuple unpacking is unused. It's
|
||||
very common for variables in a tuple unpacking assignment to be unused
|
||||
in good Python code, so warning will only create false positives.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
(x, y) = 1, 2
|
||||
''')
|
||||
|
||||
|
||||
def test_listUnpacking(self):
|
||||
"""
|
||||
Don't warn when a variable included in list unpacking is unused.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
[x, y] = [1, 2]
|
||||
''')
|
||||
|
||||
|
||||
def test_closedOver(self):
|
||||
"""
|
||||
Don't warn when the assignment is used in an inner function.
|
||||
"""
|
||||
self.flakes('''
|
||||
def barMaker():
|
||||
foo = 5
|
||||
def bar():
|
||||
return foo
|
||||
return bar
|
||||
''')
|
||||
|
||||
|
||||
def test_doubleClosedOver(self):
|
||||
"""
|
||||
Don't warn when the assignment is used in an inner function, even if
|
||||
that inner function itself is in an inner function.
|
||||
"""
|
||||
self.flakes('''
|
||||
def barMaker():
|
||||
foo = 5
|
||||
def bar():
|
||||
def baz():
|
||||
return foo
|
||||
return bar
|
||||
''')
|
||||
|
||||
|
||||
|
||||
class Python25Test(harness.Test):
|
||||
"""
|
||||
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_withStatementAttributeName(self):
|
||||
"""
|
||||
No warnings are emitted for using an attribute as the target of a
|
||||
C{with} statement.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
import foo
|
||||
with open('foo') as foo.bar:
|
||||
pass
|
||||
''')
|
||||
|
||||
|
||||
def test_withStatementSubscript(self):
|
||||
"""
|
||||
No warnings are emitted for using a subscript as the target of a
|
||||
C{with} statement.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
import foo
|
||||
with open('foo') as foo[0]:
|
||||
pass
|
||||
''')
|
||||
|
||||
|
||||
def test_withStatementSubscriptUndefined(self):
|
||||
"""
|
||||
An undefined name warning is emitted if the subscript used as the
|
||||
target of a C{with} statement is not defined.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
import foo
|
||||
with open('foo') as foo[bar]:
|
||||
pass
|
||||
''', m.UndefinedName)
|
||||
|
||||
|
||||
def test_withStatementTupleNames(self):
|
||||
"""
|
||||
No warnings are emitted for using any of the tuple of names defined by
|
||||
a C{with} statement within the suite or afterwards.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
with open('foo') as (bar, baz):
|
||||
bar, baz
|
||||
bar, baz
|
||||
''')
|
||||
|
||||
|
||||
def test_withStatementListNames(self):
|
||||
"""
|
||||
No warnings are emitted for using any of the list of names defined by a
|
||||
C{with} statement within the suite or afterwards.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
with open('foo') as [bar, baz]:
|
||||
bar, baz
|
||||
bar, baz
|
||||
''')
|
||||
|
||||
|
||||
def test_withStatementComplicatedTarget(self):
|
||||
"""
|
||||
If the target of a C{with} statement uses any or all of the valid forms
|
||||
for that part of the grammar (See
|
||||
U{http://docs.python.org/reference/compound_stmts.html#the-with-statement}),
|
||||
the names involved are checked both for definedness and any bindings
|
||||
created are respected in the suite of the statement and afterwards.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
c = d = e = g = h = i = None
|
||||
with open('foo') as [(a, b), c[d], e.f, g[h:i]]:
|
||||
a, b, c, d, e, g, h, i
|
||||
a, b, c, d, e, g, h, i
|
||||
''')
|
||||
|
||||
|
||||
def test_withStatementSingleNameUndefined(self):
|
||||
"""
|
||||
An undefined name warning is emitted if the name first defined by a
|
||||
C{with} statement is used before the C{with} statement.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
bar
|
||||
with open('foo') as bar:
|
||||
pass
|
||||
''', m.UndefinedName)
|
||||
|
||||
|
||||
def test_withStatementTupleNamesUndefined(self):
|
||||
"""
|
||||
An undefined name warning is emitted if a name first defined by a the
|
||||
tuple-unpacking form of the C{with} statement is used before the
|
||||
C{with} statement.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
baz
|
||||
with open('foo') as (bar, baz):
|
||||
pass
|
||||
''', m.UndefinedName)
|
||||
|
||||
|
||||
def test_withStatementSingleNameRedefined(self):
|
||||
"""
|
||||
A redefined name warning is emitted if a name bound by an import is
|
||||
rebound by the name defined by a C{with} statement.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
import bar
|
||||
with open('foo') as bar:
|
||||
pass
|
||||
''', m.RedefinedWhileUnused)
|
||||
|
||||
|
||||
def test_withStatementTupleNamesRedefined(self):
|
||||
"""
|
||||
A redefined name warning is emitted if a name bound by an import is
|
||||
rebound by one of the names defined by the tuple-unpacking form of a
|
||||
C{with} statement.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
import bar
|
||||
with open('foo') as (bar, baz):
|
||||
pass
|
||||
''', m.RedefinedWhileUnused)
|
||||
|
||||
|
||||
def test_withStatementUndefinedInside(self):
|
||||
"""
|
||||
An undefined name warning is emitted if a name is used inside the
|
||||
body of a C{with} statement without first being bound.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
with open('foo') as bar:
|
||||
baz
|
||||
''', m.UndefinedName)
|
||||
|
||||
|
||||
def test_withStatementNameDefinedInBody(self):
|
||||
"""
|
||||
A name defined in the body of a C{with} statement can be used after
|
||||
the body ends without warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
with open('foo') as bar:
|
||||
baz = 10
|
||||
baz
|
||||
''')
|
||||
|
||||
|
||||
def test_withStatementUndefinedInExpression(self):
|
||||
"""
|
||||
An undefined name warning is emitted if a name in the I{test}
|
||||
expression of a C{with} statement is undefined.
|
||||
"""
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
with bar as baz:
|
||||
pass
|
||||
''', m.UndefinedName)
|
||||
|
||||
self.flakes('''
|
||||
from __future__ import with_statement
|
||||
with bar as bar:
|
||||
pass
|
||||
''', m.UndefinedName)
|
||||
|
||||
|
||||
|
||||
class Python27Test(harness.Test):
|
||||
"""
|
||||
Tests for checking of syntax only available in Python 2.7 and newer.
|
||||
"""
|
||||
if version_info < (2, 7):
|
||||
skip = "Python 2.7 required for dict/set comprehension tests"
|
||||
|
||||
def test_dictComprehension(self):
|
||||
"""
|
||||
Dict comprehensions are properly handled.
|
||||
"""
|
||||
self.flakes('''
|
||||
a = {1: x for x in range(10)}
|
||||
''')
|
||||
|
||||
def test_setComprehensionAndLiteral(self):
|
||||
"""
|
||||
Set comprehensions are properly handled.
|
||||
"""
|
||||
self.flakes('''
|
||||
a = {1, 2, 3}
|
||||
b = {x for x in range(10)}
|
||||
''')
|
||||
@@ -1,185 +0,0 @@
|
||||
|
||||
"""
|
||||
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 or directory\n')
|
||||
self.assertEquals(count, 1)
|
||||
|
||||
|
||||
def test_multilineSyntaxError(self):
|
||||
"""
|
||||
Source which includes a syntax error which results in the raised
|
||||
L{SyntaxError.text} containing multiple lines of source are reported
|
||||
with only the last line of that source.
|
||||
"""
|
||||
source = """\
|
||||
def foo():
|
||||
'''
|
||||
|
||||
def bar():
|
||||
pass
|
||||
|
||||
def baz():
|
||||
'''quux'''
|
||||
"""
|
||||
|
||||
# Sanity check - SyntaxError.text should be multiple lines, if it
|
||||
# isn't, something this test was unprepared for has happened.
|
||||
def evaluate(source):
|
||||
exec source
|
||||
exc = self.assertRaises(SyntaxError, evaluate, source)
|
||||
self.assertTrue(exc.text.count('\n') > 1)
|
||||
|
||||
sourcePath = FilePath(self.mktemp())
|
||||
sourcePath.setContent(source)
|
||||
err = StringIO()
|
||||
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
|
||||
self.assertEqual(count, 1)
|
||||
|
||||
self.assertEqual(
|
||||
err.getvalue(),
|
||||
"""\
|
||||
%s:8: invalid syntax
|
||||
'''quux'''
|
||||
^
|
||||
""" % (sourcePath.path,))
|
||||
|
||||
|
||||
def test_eofSyntaxError(self):
|
||||
"""
|
||||
The error reported for source files which end prematurely causing a
|
||||
syntax error reflects the cause for the syntax error.
|
||||
"""
|
||||
source = "def foo("
|
||||
sourcePath = FilePath(self.mktemp())
|
||||
sourcePath.setContent(source)
|
||||
err = StringIO()
|
||||
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual(
|
||||
err.getvalue(),
|
||||
"""\
|
||||
%s:1: unexpected EOF while parsing
|
||||
def foo(
|
||||
^
|
||||
""" % (sourcePath.path,))
|
||||
|
||||
|
||||
def test_nonDefaultFollowsDefaultSyntaxError(self):
|
||||
"""
|
||||
Source which has a non-default argument following a default argument
|
||||
should include the line number of the syntax error. However these
|
||||
exceptions do not include an offset.
|
||||
"""
|
||||
source = """\
|
||||
def foo(bar=baz, bax):
|
||||
pass
|
||||
"""
|
||||
sourcePath = FilePath(self.mktemp())
|
||||
sourcePath.setContent(source)
|
||||
err = StringIO()
|
||||
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual(
|
||||
err.getvalue(),
|
||||
"""\
|
||||
%s:1: non-default argument follows default argument
|
||||
def foo(bar=baz, bax):
|
||||
""" % (sourcePath.path,))
|
||||
|
||||
|
||||
def test_nonKeywordAfterKeywordSyntaxError(self):
|
||||
"""
|
||||
Source which has a non-keyword argument after a keyword argument should
|
||||
include the line number of the syntax error. However these exceptions
|
||||
do not include an offset.
|
||||
"""
|
||||
source = """\
|
||||
foo(bar=baz, bax)
|
||||
"""
|
||||
sourcePath = FilePath(self.mktemp())
|
||||
sourcePath.setContent(source)
|
||||
err = StringIO()
|
||||
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual(
|
||||
err.getvalue(),
|
||||
"""\
|
||||
%s:1: non-keyword arg after keyword arg
|
||||
foo(bar=baz, bax)
|
||||
""" % (sourcePath.path,))
|
||||
|
||||
|
||||
def test_permissionDenied(self):
|
||||
"""
|
||||
If the a source file is not readable, this is reported on standard
|
||||
error.
|
||||
"""
|
||||
sourcePath = FilePath(self.mktemp())
|
||||
sourcePath.setContent('')
|
||||
sourcePath.chmod(0)
|
||||
err = StringIO()
|
||||
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
|
||||
self.assertEquals(count, 1)
|
||||
self.assertEquals(
|
||||
err.getvalue(), "%s: Permission denied\n" % (sourcePath.path,))
|
||||
|
||||
|
||||
def test_misencodedFile(self):
|
||||
"""
|
||||
If a source file contains bytes which cannot be decoded, this is
|
||||
reported on stderr.
|
||||
"""
|
||||
source = u"""\
|
||||
# coding: ascii
|
||||
x = "\N{SNOWMAN}"
|
||||
""".encode('utf-8')
|
||||
sourcePath = FilePath(self.mktemp())
|
||||
sourcePath.setContent(source)
|
||||
err = StringIO()
|
||||
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
|
||||
self.assertEquals(count, 1)
|
||||
self.assertEquals(
|
||||
err.getvalue(), "%s: problem decoding source\n" % (sourcePath.path,))
|
||||
@@ -1,265 +0,0 @@
|
||||
|
||||
from _ast import PyCF_ONLY_AST
|
||||
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
from pyflakes import messages as m, checker
|
||||
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_magicGlobalsFile(self):
|
||||
"""
|
||||
Use of the C{__file__} magic global should not emit an undefined name
|
||||
warning.
|
||||
"""
|
||||
self.flakes('__file__')
|
||||
|
||||
|
||||
def test_magicGlobalsBuiltins(self):
|
||||
"""
|
||||
Use of the C{__builtins__} magic global should not emit an undefined
|
||||
name warning.
|
||||
"""
|
||||
self.flakes('__builtins__')
|
||||
|
||||
|
||||
def test_magicGlobalsName(self):
|
||||
"""
|
||||
Use of the C{__name__} magic global should not emit an undefined name
|
||||
warning.
|
||||
"""
|
||||
self.flakes('__name__')
|
||||
|
||||
|
||||
def test_magicGlobalsPath(self):
|
||||
"""
|
||||
Use of the C{__path__} magic global should not emit an undefined name
|
||||
warning, if you refer to it from a file called __init__.py.
|
||||
"""
|
||||
self.flakes('__path__', m.UndefinedName)
|
||||
self.flakes('__path__', filename='package/__init__.py')
|
||||
|
||||
|
||||
def test_globalImportStar(self):
|
||||
'''Can't find undefined names with import *'''
|
||||
self.flakes('from fu import *; bar', m.ImportStarUsed)
|
||||
|
||||
def test_localImportStar(self):
|
||||
'''A local import * still allows undefined names to be found in upper scopes'''
|
||||
self.flakes('''
|
||||
def a():
|
||||
from fu import *
|
||||
bar
|
||||
''', m.ImportStarUsed, m.UndefinedName)
|
||||
|
||||
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_globalInGlobalScope(self):
|
||||
"""
|
||||
A global statement in the global scope is ignored.
|
||||
"""
|
||||
self.flakes('''
|
||||
global x
|
||||
def foo():
|
||||
print x
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_del(self):
|
||||
'''del deletes bindings'''
|
||||
self.flakes('a = 1; del a; a', m.UndefinedName)
|
||||
|
||||
def test_delGlobal(self):
|
||||
'''del a global binding from a function'''
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def f():
|
||||
global a
|
||||
del a
|
||||
a
|
||||
''')
|
||||
|
||||
def test_delUndefined(self):
|
||||
'''del an undefined name'''
|
||||
self.flakes('del a', m.UndefinedName)
|
||||
|
||||
def test_globalFromNestedScope(self):
|
||||
'''global names are available from nested scopes'''
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def b():
|
||||
def c():
|
||||
a
|
||||
''')
|
||||
|
||||
def test_laterRedefinedGlobalFromNestedScope(self):
|
||||
"""
|
||||
Test that referencing a local name that shadows a global, before it is
|
||||
defined, generates a warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def fun():
|
||||
a
|
||||
a = 2
|
||||
return a
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
def test_laterRedefinedGlobalFromNestedScope2(self):
|
||||
"""
|
||||
Test that referencing a local name in a nested scope that shadows a
|
||||
global declared in an enclosing scope, before it is defined, generates
|
||||
a warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def fun():
|
||||
global a
|
||||
def fun2():
|
||||
a
|
||||
a = 2
|
||||
return a
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
|
||||
def test_intermediateClassScopeIgnored(self):
|
||||
"""
|
||||
If a name defined in an enclosing scope is shadowed by a local variable
|
||||
and the name is used locally before it is bound, an unbound local
|
||||
warning is emitted, even if there is a class scope between the enclosing
|
||||
scope and the local scope.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
x = 1
|
||||
class g:
|
||||
def h(self):
|
||||
a = x
|
||||
x = None
|
||||
print x, a
|
||||
print x
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
|
||||
def test_doubleNestingReportsClosestName(self):
|
||||
"""
|
||||
Test that referencing a local name in a nested scope that shadows a
|
||||
variable declared in two different outer scopes before it is defined
|
||||
in the innermost scope generates an UnboundLocal warning which
|
||||
refers to the nearest shadowed name.
|
||||
"""
|
||||
exc = self.flakes('''
|
||||
def a():
|
||||
x = 1
|
||||
def b():
|
||||
x = 2 # line 5
|
||||
def c():
|
||||
x
|
||||
x = 3
|
||||
return x
|
||||
return x
|
||||
return x
|
||||
''', m.UndefinedLocal).messages[0]
|
||||
self.assertEqual(exc.message_args, ('x', 5))
|
||||
|
||||
|
||||
def test_laterRedefinedGlobalFromNestedScope3(self):
|
||||
"""
|
||||
Test that referencing a local name in a nested scope that shadows a
|
||||
global, before it is defined, generates a warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
def fun():
|
||||
a = 1
|
||||
def fun2():
|
||||
a
|
||||
a = 1
|
||||
return a
|
||||
return a
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
def test_nestedClass(self):
|
||||
'''nested classes can access enclosing scope'''
|
||||
self.flakes('''
|
||||
def f(foo):
|
||||
class C:
|
||||
bar = foo
|
||||
def f(self):
|
||||
return foo
|
||||
return C()
|
||||
|
||||
f(123).f()
|
||||
''')
|
||||
|
||||
def test_badNestedClass(self):
|
||||
'''free variables in nested classes must bind at class creation'''
|
||||
self.flakes('''
|
||||
def f():
|
||||
class C:
|
||||
bar = foo
|
||||
foo = 456
|
||||
return foo
|
||||
f()
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_definedAsStarArgs(self):
|
||||
'''star and double-star arg names are defined'''
|
||||
self.flakes('''
|
||||
def f(a, *b, **c):
|
||||
print a, b, c
|
||||
''')
|
||||
|
||||
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)')
|
||||
|
||||
|
||||
|
||||
class NameTests(TestCase):
|
||||
"""
|
||||
Tests for some extra cases of name handling.
|
||||
"""
|
||||
def test_impossibleContext(self):
|
||||
"""
|
||||
A Name node with an unrecognized context results in a RuntimeError being
|
||||
raised.
|
||||
"""
|
||||
tree = compile("x = 10", "<test>", "exec", PyCF_ONLY_AST)
|
||||
# Make it into something unrecognizable.
|
||||
tree.body[0].targets[0].ctx = object()
|
||||
self.assertRaises(RuntimeError, checker.Checker, tree)
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# (c) 2005-2009 Divmod, Inc. See LICENSE file for details
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
setup(
|
||||
name="pyflakes",
|
||||
license="MIT",
|
||||
version="0.4.0",
|
||||
description="passive checker of Python programs",
|
||||
author="Phil Frost",
|
||||
maintainer="Moe Aboulkheir",
|
||||
maintainer_email="moe@divmod.com",
|
||||
url="http://www.divmod.org/trac/wiki/DivmodPyflakes",
|
||||
packages=["pyflakes", "pyflakes.scripts", "pyflakes.test"],
|
||||
scripts=["bin/pyflakes"],
|
||||
long_description="""Pyflakes is program to analyze Python programs and detect various errors. It
|
||||
works by parsing the source file, not importing it, so it is safe to use on
|
||||
modules with side effects. It's also much faster.""",
|
||||
classifiers=[
|
||||
"Development Status :: 6 - Mature",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Software Development",
|
||||
"Topic :: Utilities",
|
||||
])
|
||||
@@ -1,146 +0,0 @@
|
||||
" File: pylint_fn.vim
|
||||
" Author: Roman 'gryf' Dobosz (gryf73 at gmail.com)
|
||||
" Version: 1.0
|
||||
" Last Modified: 2010-09-11
|
||||
"
|
||||
" Description: " {{{
|
||||
"
|
||||
" Overview
|
||||
" --------
|
||||
" This plugin provides ":Pylint" command, which put pylint result into quickfix
|
||||
" buffer. This function does not uses pylint[1] command line utility, only
|
||||
" python pylint.lint module is used instead. So it makes the pylint
|
||||
" egg/package required for running this script.
|
||||
"
|
||||
" This script uses python, therefore VIm should be compiled with python
|
||||
" support. You can check it by issuing ":version" command, and search for
|
||||
" "+python" inside features list.
|
||||
"
|
||||
" Couple of ideas was taken from pyflakes.vim[2] plugin.
|
||||
"
|
||||
" Installation
|
||||
" ------------
|
||||
" 1. Copy the pylint_fn.vim file to the $HOME/.vim/ftplugin/python or
|
||||
" $HOME/vimfiles/ftplugin/python or $VIM/vimfiles/ftplugin/python
|
||||
" directory. If python directory doesn't exists, it should be created.
|
||||
" Refer to the following Vim help topics for more information about Vim
|
||||
" plugins:
|
||||
" :help add-plugin
|
||||
" :help add-global-plugin
|
||||
" :help runtimepath
|
||||
" 2. It should be possible to import pylint from python interpreter (it should
|
||||
" report no error):
|
||||
" >>> import pylint
|
||||
" >>>
|
||||
" If there are errors, install pylint first. Simplest way to do it, is to
|
||||
" use easy_install[3] shell command as a root:
|
||||
" # easy_install pylint
|
||||
" 3. Restart Vim.
|
||||
" 4. You can now use the ":Pylint" which will examine current python buffer
|
||||
" and open quickfix buffer with errors if any.
|
||||
"
|
||||
" [1] http://www.logilab.org/project/pylint
|
||||
" [2] http://www.vim.org/scripts/script.php?script_id=2441
|
||||
" [3] http://pypi.python.org/pypi/setuptools
|
||||
" }}}
|
||||
|
||||
if exists("b:did_pylint_plugin")
|
||||
finish " only load once
|
||||
else
|
||||
let b:did_pylint_plugin = 1
|
||||
endif
|
||||
|
||||
if !exists("b:did_pylint_init")
|
||||
let b:did_pylint_init = 0
|
||||
|
||||
if !has('python')
|
||||
echoerr "Error: pylint_fn.vim requires Vim to be compiled with +python"
|
||||
finish
|
||||
endif
|
||||
|
||||
python << EOF
|
||||
import vim
|
||||
import sys
|
||||
from StringIO import StringIO
|
||||
|
||||
try:
|
||||
from pylint import lint
|
||||
from pylint.reporters.text import TextReporter
|
||||
except ImportError:
|
||||
raise AssertionError('Pylint is required for this plugin')
|
||||
|
||||
class VImPylint(object):
|
||||
|
||||
sys_stderr = sys.stderr
|
||||
dummy_stderr = StringIO()
|
||||
conf_msg = 'No config file found, using default configuration\n'
|
||||
|
||||
@classmethod
|
||||
def run(self):
|
||||
"""execute pylint and fill the quickfix"""
|
||||
|
||||
# clear QF window
|
||||
vim.command('call setqflist([])')
|
||||
|
||||
# args
|
||||
args = ['-rn', # display only the messages instead of full report
|
||||
'-iy', # Include message's id in output
|
||||
vim.current.buffer.name]
|
||||
|
||||
buf = StringIO() # file-like buffer, instead of stdout
|
||||
reporter = TextReporter(buf)
|
||||
|
||||
sys.stderr = self.dummy_stderr
|
||||
lint.Run(args, reporter=reporter, exit=False)
|
||||
sys.stderr = self.sys_stderr
|
||||
|
||||
self.dummy_stderr.seek(0)
|
||||
error_list = self.dummy_stderr.readlines()
|
||||
self.dummy_stderr.truncate(0)
|
||||
if error_list and self.conf_msg in error_list:
|
||||
error_list.remove(self.conf_msg)
|
||||
if error_list:
|
||||
raise Exception(''.join(error_list))
|
||||
|
||||
buf.seek(0)
|
||||
|
||||
bufnr = vim.current.buffer.number
|
||||
code_line = {}
|
||||
error_list = []
|
||||
|
||||
carriage_re = re.compile(r'\s*\^+$')
|
||||
error_re = re.compile(r'^([C,R,W,E,F].+):\s+?([0-9]+):?.*:\s(.*)$')
|
||||
|
||||
for line in buf:
|
||||
line = line.rstrip() # remove trailing newline character
|
||||
|
||||
if error_re.match(line):
|
||||
if code_line:
|
||||
code_line['bufnr'] = bufnr
|
||||
error_list.append(code_line)
|
||||
code_line = {}
|
||||
|
||||
code_line['type'], code_line['lnum'], code_line['text'] = \
|
||||
error_re.match(line).groups()
|
||||
|
||||
if carriage_re.match(line) and code_line:
|
||||
code_line['col'] = carriage_re.match(line).group().find('^') \
|
||||
+ 1
|
||||
vim.command('call setqflist(%s)' % str(error_list))
|
||||
if error_list:
|
||||
vim.command('copen')
|
||||
EOF
|
||||
let b:did_pylint_init = 1
|
||||
endif
|
||||
|
||||
if !exists('*s:Pylint')
|
||||
function s:Pylint()
|
||||
python << EOF
|
||||
VImPylint.run()
|
||||
EOF
|
||||
endfunction
|
||||
endif
|
||||
|
||||
if !exists(":Pylint")
|
||||
command Pylint call s:Pylint()
|
||||
endif
|
||||
@@ -1,447 +0,0 @@
|
||||
" -*- vim -*-
|
||||
" FILE: python_fn.vim
|
||||
" LAST MODIFICATION: 2008-08-28 8:19pm
|
||||
" (C) Copyright 2001-2005 Mikael Berthe <bmikael@lists.lilotux.net>
|
||||
" Maintained by Jon Franklin <jvfranklin@gmail.com>
|
||||
" Version: 1.13
|
||||
|
||||
" USAGE:
|
||||
"
|
||||
" Save this file to $VIMFILES/ftplugin/python.vim. You can have multiple
|
||||
" python ftplugins by creating $VIMFILES/ftplugin/python and saving your
|
||||
" ftplugins in that directory. If saving this to the global ftplugin
|
||||
" directory, this is the recommended method, since vim ships with an
|
||||
" ftplugin/python.vim file already.
|
||||
" You can set the global variable "g:py_select_leading_comments" to 0
|
||||
" if you don't want to select comments preceding a declaration (these
|
||||
" are usually the description of the function/class).
|
||||
" You can set the global variable "g:py_select_trailing_comments" to 0
|
||||
" if you don't want to select comments at the end of a function/class.
|
||||
" If these variables are not defined, both leading and trailing comments
|
||||
" are selected.
|
||||
" Example: (in your .vimrc) "let g:py_select_leading_comments = 0"
|
||||
" You may want to take a look at the 'shiftwidth' option for the
|
||||
" shift commands...
|
||||
"
|
||||
" REQUIREMENTS:
|
||||
" vim (>= 7)
|
||||
"
|
||||
" Shortcuts:
|
||||
" ]t -- Jump to beginning of block
|
||||
" ]e -- Jump to end of block
|
||||
" ]v -- Select (Visual Line Mode) block
|
||||
" ]< -- Shift block to left
|
||||
" ]> -- Shift block to right
|
||||
" ]# -- Comment selection
|
||||
" ]u -- Uncomment selection
|
||||
" ]c -- Select current/previous class
|
||||
" ]d -- Select current/previous function
|
||||
" ]<up> -- Jump to previous line with the same/lower indentation
|
||||
" ]<down> -- Jump to next line with the same/lower indentation
|
||||
|
||||
" Only do this when not done yet for this buffer
|
||||
if exists("b:loaded_py_ftplugin")
|
||||
finish
|
||||
endif
|
||||
let b:loaded_py_ftplugin = 1
|
||||
|
||||
map ]t :PBoB<CR>
|
||||
vmap ]t :<C-U>PBOB<CR>m'gv``
|
||||
map ]e :PEoB<CR>
|
||||
vmap ]e :<C-U>PEoB<CR>m'gv``
|
||||
|
||||
map ]v ]tV]e
|
||||
map ]< ]tV]e<
|
||||
vmap ]< <
|
||||
map ]> ]tV]e>
|
||||
vmap ]> >
|
||||
|
||||
map ]# :call PythonCommentSelection()<CR>
|
||||
vmap ]# :call PythonCommentSelection()<CR>
|
||||
map ]u :call PythonUncommentSelection()<CR>
|
||||
vmap ]u :call PythonUncommentSelection()<CR>
|
||||
|
||||
" gryf: change mapping for class selection
|
||||
map ]C :call PythonSelectObject("class")<CR>
|
||||
map ]d :call PythonSelectObject("function")<CR>
|
||||
|
||||
map ]<up> :call PythonNextLine(-1)<CR>
|
||||
map ]<down> :call PythonNextLine(1)<CR>
|
||||
" You may prefer use <s-up> and <s-down>... :-)
|
||||
|
||||
" jump to previous class
|
||||
map ]J :call PythonDec("class", -1)<CR>
|
||||
vmap ]J :call PythonDec("class", -1)<CR>
|
||||
|
||||
" jump to next class
|
||||
map ]j :call PythonDec("class", 1)<CR>
|
||||
vmap ]j :call PythonDec("class", 1)<CR>
|
||||
|
||||
" jump to previous function
|
||||
map ]F :call PythonDec("function", -1)<CR>
|
||||
vmap ]F :call PythonDec("function", -1)<CR>
|
||||
|
||||
" jump to next function
|
||||
map ]f :call PythonDec("function", 1)<CR>
|
||||
vmap ]f :call PythonDec("function", 1)<CR>
|
||||
|
||||
|
||||
|
||||
" Menu entries
|
||||
nmenu <silent> &Python.Update\ IM-Python\ Menu
|
||||
\:call UpdateMenu()<CR>
|
||||
nmenu &Python.-Sep1- :
|
||||
nmenu <silent> &Python.Beginning\ of\ Block<Tab>[t
|
||||
\]t
|
||||
nmenu <silent> &Python.End\ of\ Block<Tab>]e
|
||||
\]e
|
||||
nmenu &Python.-Sep2- :
|
||||
nmenu <silent> &Python.Shift\ Block\ Left<Tab>]<
|
||||
\]<
|
||||
vmenu <silent> &Python.Shift\ Block\ Left<Tab>]<
|
||||
\]<
|
||||
nmenu <silent> &Python.Shift\ Block\ Right<Tab>]>
|
||||
\]>
|
||||
vmenu <silent> &Python.Shift\ Block\ Right<Tab>]>
|
||||
\]>
|
||||
nmenu &Python.-Sep3- :
|
||||
vmenu <silent> &Python.Comment\ Selection<Tab>]#
|
||||
\]#
|
||||
nmenu <silent> &Python.Comment\ Selection<Tab>]#
|
||||
\]#
|
||||
vmenu <silent> &Python.Uncomment\ Selection<Tab>]u
|
||||
\]u
|
||||
nmenu <silent> &Python.Uncomment\ Selection<Tab>]u
|
||||
\]u
|
||||
nmenu &Python.-Sep4- :
|
||||
nmenu <silent> &Python.Previous\ Class<Tab>]J
|
||||
\]J
|
||||
nmenu <silent> &Python.Next\ Class<Tab>]j
|
||||
\]j
|
||||
nmenu <silent> &Python.Previous\ Function<Tab>]F
|
||||
\]F
|
||||
nmenu <silent> &Python.Next\ Function<Tab>]f
|
||||
\]f
|
||||
nmenu &Python.-Sep5- :
|
||||
nmenu <silent> &Python.Select\ Block<Tab>]v
|
||||
\]v
|
||||
nmenu <silent> &Python.Select\ Function<Tab>]d
|
||||
\]d
|
||||
nmenu <silent> &Python.Select\ Class<Tab>]c
|
||||
\]c
|
||||
nmenu &Python.-Sep6- :
|
||||
nmenu <silent> &Python.Previous\ Line\ wrt\ indent<Tab>]<up>
|
||||
\]<up>
|
||||
nmenu <silent> &Python.Next\ Line\ wrt\ indent<Tab>]<down>
|
||||
\]<down>
|
||||
|
||||
:com! PBoB execute "normal ".PythonBoB(line('.'), -1, 1)."G"
|
||||
:com! PEoB execute "normal ".PythonBoB(line('.'), 1, 1)."G"
|
||||
:com! UpdateMenu call UpdateMenu()
|
||||
|
||||
|
||||
" Go to a block boundary (-1: previous, 1: next)
|
||||
" If force_sel_comments is true, 'g:py_select_trailing_comments' is ignored
|
||||
function! PythonBoB(line, direction, force_sel_comments)
|
||||
let ln = a:line
|
||||
let ind = indent(ln)
|
||||
let mark = ln
|
||||
let indent_valid = strlen(getline(ln))
|
||||
let ln = ln + a:direction
|
||||
if (a:direction == 1) && (!a:force_sel_comments) &&
|
||||
\ exists("g:py_select_trailing_comments") &&
|
||||
\ (!g:py_select_trailing_comments)
|
||||
let sel_comments = 0
|
||||
else
|
||||
let sel_comments = 1
|
||||
endif
|
||||
|
||||
while((ln >= 1) && (ln <= line('$')))
|
||||
if (sel_comments) || (match(getline(ln), "^\\s*#") == -1)
|
||||
if (!indent_valid)
|
||||
let indent_valid = strlen(getline(ln))
|
||||
let ind = indent(ln)
|
||||
let mark = ln
|
||||
else
|
||||
if (strlen(getline(ln)))
|
||||
if (indent(ln) < ind)
|
||||
break
|
||||
endif
|
||||
let mark = ln
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
let ln = ln + a:direction
|
||||
endwhile
|
||||
|
||||
return mark
|
||||
endfunction
|
||||
|
||||
|
||||
" Go to previous (-1) or next (1) class/function definition
|
||||
function! PythonDec(obj, direction)
|
||||
if (a:obj == "class")
|
||||
let objregexp = "^\\s*class\\s\\+[a-zA-Z0-9_]\\+"
|
||||
\ . "\\s*\\((\\([a-zA-Z0-9_,. \\t\\n]\\)*)\\)\\=\\s*:"
|
||||
else
|
||||
let objregexp = "^\\s*def\\s\\+[a-zA-Z0-9_]\\+\\s*(\\_[^:#]*)\\s*:"
|
||||
endif
|
||||
let flag = "W"
|
||||
if (a:direction == -1)
|
||||
let flag = flag."b"
|
||||
endif
|
||||
let res = search(objregexp, flag)
|
||||
endfunction
|
||||
|
||||
|
||||
" Comment out selected lines
|
||||
" commentString is inserted in non-empty lines, and should be aligned with
|
||||
" the block
|
||||
function! PythonCommentSelection() range
|
||||
let commentString = "#"
|
||||
let cl = a:firstline
|
||||
let ind = 1000 " I hope nobody use so long lines! :)
|
||||
|
||||
" Look for smallest indent
|
||||
while (cl <= a:lastline)
|
||||
if strlen(getline(cl))
|
||||
let cind = indent(cl)
|
||||
let ind = ((ind < cind) ? ind : cind)
|
||||
endif
|
||||
let cl = cl + 1
|
||||
endwhile
|
||||
if (ind == 1000)
|
||||
let ind = 1
|
||||
else
|
||||
let ind = ind + 1
|
||||
endif
|
||||
|
||||
let cl = a:firstline
|
||||
execute ":".cl
|
||||
" Insert commentString in each non-empty line, in column ind
|
||||
while (cl <= a:lastline)
|
||||
if strlen(getline(cl))
|
||||
execute "normal ".ind."|i".commentString
|
||||
endif
|
||||
execute "normal \<Down>"
|
||||
let cl = cl + 1
|
||||
endwhile
|
||||
endfunction
|
||||
|
||||
" Uncomment selected lines
|
||||
function! PythonUncommentSelection() range
|
||||
" commentString could be different than the one from CommentSelection()
|
||||
" For example, this could be "# \\="
|
||||
let commentString = "#"
|
||||
let cl = a:firstline
|
||||
while (cl <= a:lastline)
|
||||
let ul = substitute(getline(cl),
|
||||
\"\\(\\s*\\)".commentString."\\(.*\\)$", "\\1\\2", "")
|
||||
call setline(cl, ul)
|
||||
let cl = cl + 1
|
||||
endwhile
|
||||
endfunction
|
||||
|
||||
|
||||
" Select an object ("class"/"function")
|
||||
function! PythonSelectObject(obj)
|
||||
" Go to the object declaration
|
||||
normal $
|
||||
call PythonDec(a:obj, -1)
|
||||
let beg = line('.')
|
||||
|
||||
if !exists("g:py_select_leading_comments") || (g:py_select_leading_comments)
|
||||
let decind = indent(beg)
|
||||
let cl = beg
|
||||
while (cl>1)
|
||||
let cl = cl - 1
|
||||
if (indent(cl) == decind) && (getline(cl)[decind] == "#")
|
||||
let beg = cl
|
||||
else
|
||||
break
|
||||
endif
|
||||
endwhile
|
||||
endif
|
||||
|
||||
if (a:obj == "class")
|
||||
let eod = "\\(^\\s*class\\s\\+[a-zA-Z0-9_]\\+\\s*"
|
||||
\ . "\\((\\([a-zA-Z0-9_,. \\t\\n]\\)*)\\)\\=\\s*\\)\\@<=:"
|
||||
else
|
||||
let eod = "\\(^\\s*def\\s\\+[a-zA-Z0-9_]\\+\\s*(\\_[^:#]*)\\s*\\)\\@<=:"
|
||||
endif
|
||||
" Look for the end of the declaration (not always the same line!)
|
||||
call search(eod, "")
|
||||
|
||||
" Is it a one-line definition?
|
||||
if match(getline('.'), "^\\s*\\(#.*\\)\\=$", col('.')) == -1
|
||||
let cl = line('.')
|
||||
execute ":".beg
|
||||
execute "normal V".cl."G"
|
||||
else
|
||||
" Select the whole block
|
||||
execute "normal \<Down>"
|
||||
let cl = line('.')
|
||||
execute ":".beg
|
||||
execute "normal V".PythonBoB(cl, 1, 0)."G"
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" Jump to the next line with the same (or lower) indentation
|
||||
" Useful for moving between "if" and "else", for example.
|
||||
function! PythonNextLine(direction)
|
||||
let ln = line('.')
|
||||
let ind = indent(ln)
|
||||
let indent_valid = strlen(getline(ln))
|
||||
let ln = ln + a:direction
|
||||
|
||||
while((ln >= 1) && (ln <= line('$')))
|
||||
if (!indent_valid) && strlen(getline(ln))
|
||||
break
|
||||
else
|
||||
if (strlen(getline(ln)))
|
||||
if (indent(ln) <= ind)
|
||||
break
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
let ln = ln + a:direction
|
||||
endwhile
|
||||
|
||||
execute "normal ".ln."G"
|
||||
endfunction
|
||||
|
||||
function! UpdateMenu()
|
||||
" delete menu if it already exists, then rebuild it.
|
||||
" this is necessary in case you've got multiple buffers open
|
||||
" a future enhancement to this would be to make the menu aware of
|
||||
" all buffers currently open, and group classes and functions by buffer
|
||||
if exists("g:menuran")
|
||||
aunmenu IM-Python
|
||||
endif
|
||||
let restore_fe = &foldenable
|
||||
set nofoldenable
|
||||
" preserve disposition of window and cursor
|
||||
let cline=line('.')
|
||||
let ccol=col('.') - 1
|
||||
norm H
|
||||
let hline=line('.')
|
||||
" create the menu
|
||||
call MenuBuilder()
|
||||
" restore disposition of window and cursor
|
||||
exe "norm ".hline."Gzt"
|
||||
let dnscroll=cline-hline
|
||||
exe "norm ".dnscroll."j".ccol."l"
|
||||
let &foldenable = restore_fe
|
||||
endfunction
|
||||
|
||||
function! MenuBuilder()
|
||||
norm gg0
|
||||
let currentclass = -1
|
||||
let classlist = []
|
||||
let parentclass = ""
|
||||
while line(".") < line("$")
|
||||
" search for a class or function
|
||||
if match ( getline("."), '^\s*class\s\+[_a-zA-Z].*\|^\s*def\s\+[_a-zA-Z].*' ) != -1
|
||||
norm ^
|
||||
let linenum = line('.')
|
||||
let indentcol = col('.')
|
||||
norm "nye
|
||||
let classordef=@n
|
||||
norm w"nywge
|
||||
let objname=@n
|
||||
let parentclass = FindParentClass(classlist, indentcol)
|
||||
if classordef == "class"
|
||||
call AddClass(objname, linenum, parentclass)
|
||||
else " this is a function
|
||||
call AddFunction(objname, linenum, parentclass)
|
||||
endif
|
||||
" We actually created a menu, so lets set the global variable
|
||||
let g:menuran=1
|
||||
call RebuildClassList(classlist, [objname, indentcol], classordef)
|
||||
endif " line matched
|
||||
norm j
|
||||
endwhile
|
||||
endfunction
|
||||
|
||||
" classlist contains the list of nested classes we are in.
|
||||
" in most cases it will be empty or contain a single class
|
||||
" but where a class is nested within another, it will contain 2 or more
|
||||
" this function adds or removes classes from the list based on indentation
|
||||
function! RebuildClassList(classlist, newclass, classordef)
|
||||
let i = len(a:classlist) - 1
|
||||
while i > -1
|
||||
if a:newclass[1] <= a:classlist[i][1]
|
||||
call remove(a:classlist, i)
|
||||
endif
|
||||
let i = i - 1
|
||||
endwhile
|
||||
if a:classordef == "class"
|
||||
call add(a:classlist, a:newclass)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" we found a class or function, determine its parent class based on
|
||||
" indentation and what's contained in classlist
|
||||
function! FindParentClass(classlist, indentcol)
|
||||
let i = 0
|
||||
let parentclass = ""
|
||||
while i < len(a:classlist)
|
||||
if a:indentcol <= a:classlist[i][1]
|
||||
break
|
||||
else
|
||||
if len(parentclass) == 0
|
||||
let parentclass = a:classlist[i][0]
|
||||
else
|
||||
let parentclass = parentclass.'\.'.a:classlist[i][0]
|
||||
endif
|
||||
endif
|
||||
let i = i + 1
|
||||
endwhile
|
||||
return parentclass
|
||||
endfunction
|
||||
|
||||
" add a class to the menu
|
||||
function! AddClass(classname, lineno, parentclass)
|
||||
if len(a:parentclass) > 0
|
||||
let classstring = a:parentclass.'\.'.a:classname
|
||||
else
|
||||
let classstring = a:classname
|
||||
endif
|
||||
exe 'menu IM-Python.classes.'.classstring.' :call <SID>JumpToAndUnfold('.a:lineno.')<CR>'
|
||||
endfunction
|
||||
|
||||
" add a function to the menu, grouped by member class
|
||||
function! AddFunction(functionname, lineno, parentclass)
|
||||
if len(a:parentclass) > 0
|
||||
let funcstring = a:parentclass.'.'.a:functionname
|
||||
else
|
||||
let funcstring = a:functionname
|
||||
endif
|
||||
exe 'menu IM-Python.functions.'.funcstring.' :call <SID>JumpToAndUnfold('.a:lineno.')<CR>'
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:JumpToAndUnfold(line)
|
||||
" Go to the right line
|
||||
execute 'normal '.a:line.'gg'
|
||||
" Check to see if we are in a fold
|
||||
let lvl = foldlevel(a:line)
|
||||
if lvl != 0
|
||||
" and if so, then expand the fold out, other wise, ignore this part.
|
||||
execute 'normal 15zo'
|
||||
endif
|
||||
endfunction
|
||||
|
||||
"" This one will work only on vim 6.2 because of the try/catch expressions.
|
||||
" function! s:JumpToAndUnfoldWithExceptions(line)
|
||||
" try
|
||||
" execute 'normal '.a:line.'gg15zo'
|
||||
" catch /^Vim\((\a\+)\)\=:E490:/
|
||||
" " Do nothing, just consume the error
|
||||
" endtry
|
||||
"endfunction
|
||||
|
||||
|
||||
" vim:set et sts=2 sw=2:
|
||||
|
||||
@@ -1,763 +0,0 @@
|
||||
" File: pythonhelper.vim
|
||||
" Author: Michal Vitecek <fuf-at-mageo-dot-cz>
|
||||
" Version: 0.83
|
||||
" Last Modified: Jan 4, 2010
|
||||
"
|
||||
" Overview
|
||||
" --------
|
||||
" Vim script to help moving around in larger Python source files. It displays
|
||||
" current class, method or function the cursor is placed in on the status
|
||||
" line for every python file. It's more clever than Yegappan Lakshmanan's
|
||||
" taglist.vim because it takes into account indetation and comments to
|
||||
" determine what tag the cursor is placed in.
|
||||
"
|
||||
" Requirements
|
||||
" ------------
|
||||
" This script needs only VIM compiled with Python interpreter. It doesn't rely
|
||||
" on exuberant ctags utility. You can determine whether your VIM has Python
|
||||
" support by issuing command :ver and looking for +python in the list of
|
||||
" features.
|
||||
"
|
||||
" Installation
|
||||
" ------------
|
||||
" 1. Make sure your Vim has python feature on (+python). If not, you will need
|
||||
" to recompile it with --with-pythoninterp option to the configure script
|
||||
" 2. Copy script pythonhelper.vim to the $HOME/.vim/plugin directory
|
||||
" 3. Run Vim and open any python file.
|
||||
"
|
||||
python << EOS
|
||||
|
||||
# import of required modules {{{
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import vim
|
||||
# }}}
|
||||
|
||||
|
||||
# global dictionaries of tags and their line numbers, keys are buffer numbers {{{
|
||||
TAGS = {}
|
||||
TAGLINENUMBERS = {}
|
||||
BUFFERTICKS = {}
|
||||
# }}}
|
||||
|
||||
|
||||
# class PythonTag() {{{
|
||||
class PythonTag(object):
|
||||
# DOC {{{
|
||||
"""A simple storage class representing a python tag.
|
||||
"""
|
||||
# }}}
|
||||
|
||||
|
||||
# STATIC VARIABLES {{{
|
||||
|
||||
# possible tag types {{{
|
||||
TT_CLASS = 0
|
||||
TT_METHOD = 1
|
||||
TT_FUNCTION = 2
|
||||
# }}}
|
||||
|
||||
# tag type names {{{
|
||||
TAG_TYPE_NAME = {
|
||||
TT_CLASS : "class",
|
||||
TT_METHOD : "method",
|
||||
TT_FUNCTION : "function",
|
||||
}
|
||||
# }}}
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
# METHODS {{{
|
||||
|
||||
def __init__(self, type, name, fullName, lineNumber, indentLevel):
|
||||
# DOC {{{
|
||||
"""Initializes instances of PythonTag().
|
||||
|
||||
Parameters
|
||||
|
||||
type -- tag type
|
||||
|
||||
name -- short tag name
|
||||
|
||||
fullName -- full tag name (in dotted notation)
|
||||
|
||||
lineNumber -- line number on which the tag starts
|
||||
|
||||
indentLevel -- indentation level of the tag
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# remember the settings {{{
|
||||
self.type = type
|
||||
self.name = name
|
||||
self.fullName = fullName
|
||||
self.lineNumber = lineNumber
|
||||
self.indentLevel = indentLevel
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
|
||||
def __str__(self):
|
||||
# DOC {{{
|
||||
"""Returns a string representation of the tag.
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
return "%s (%s) [%s, %u, %u]" % (self.name, PythonTag.TAG_TYPE_NAME[self.type],
|
||||
self.fullName, self.lineNumber, self.indentLevel,)
|
||||
# }}}
|
||||
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
|
||||
# class SimplePythonTagsParser() {{{
|
||||
class SimplePythonTagsParser(object):
|
||||
# DOC {{{
|
||||
"""Provides a simple python tag parser.
|
||||
"""
|
||||
# }}}
|
||||
|
||||
|
||||
# STATIC VARIABLES {{{
|
||||
|
||||
# how many chars a single tab represents (visually)
|
||||
TABSIZE = 8
|
||||
# regexp used to extract indentation and strip comments
|
||||
COMMENTS_INDENT_RE = re.compile('([ \t]*)([^\n#]*).*')
|
||||
# regexp used to extract a class name
|
||||
CLASS_RE = re.compile('class[ \t]+([^(:]+).*')
|
||||
# regexp used to extract a method or function name
|
||||
METHOD_RE = re.compile('def[ \t]+([^(]+).*')
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
# METHODS {{{
|
||||
|
||||
def __init__(self, source):
|
||||
# DOC {{{
|
||||
"""Initializes instances of SimplePythonTagsParser().
|
||||
|
||||
Parameters
|
||||
|
||||
source -- source for which the tags will be generated. It must
|
||||
provide callable method readline (i.e. as file objects do).
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# make sure source has readline() method {{{
|
||||
if ((hasattr(source, 'readline') == 0) or
|
||||
(callable(source.readline) == 0)):
|
||||
raise AttributeError("Source must have callable readline method.")
|
||||
# }}}
|
||||
|
||||
# remember what the source is
|
||||
self.source = source
|
||||
# }}}
|
||||
|
||||
|
||||
def getTags(self):
|
||||
# DOC {{{
|
||||
"""Determines all the tags for the buffer. Returns a tuple in format
|
||||
(tagLineNumbers, tags,).
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# initialize the resulting list of the tag line numbers and the tag information {{{
|
||||
tagLineNumbers = []
|
||||
tags = {}
|
||||
# }}}
|
||||
|
||||
# initalize local auxiliary variables {{{
|
||||
tagsStack = []
|
||||
lineNumber = 0
|
||||
# }}}
|
||||
|
||||
# go through all the lines in the source and localize all python tags in it {{{
|
||||
while 1:
|
||||
# get next line
|
||||
line = self.source.readline()
|
||||
|
||||
# finish if this is the end of the source {{{
|
||||
if (line == ''):
|
||||
break
|
||||
# }}}
|
||||
|
||||
# increase the line number
|
||||
lineNumber += 1
|
||||
|
||||
# extract the line indentation characters and its content {{{
|
||||
lineMatch = self.COMMENTS_INDENT_RE.match(line)
|
||||
lineContent = lineMatch.group(2)
|
||||
# }}}
|
||||
|
||||
# handle the class tag {{{
|
||||
# match for the class tag
|
||||
tagMatch = self.CLASS_RE.match(lineContent)
|
||||
|
||||
# if the class tag has been found, store some information on it {{{
|
||||
if (tagMatch):
|
||||
currentTag = self.getPythonTag(tagsStack, lineNumber, lineMatch.group(1),
|
||||
tagMatch.group(1), self.tagClassTypeDecidingMethod)
|
||||
tagLineNumbers.append(lineNumber)
|
||||
tags[lineNumber] = currentTag
|
||||
# }}}
|
||||
# }}}
|
||||
# handle the function/method/none tag {{{
|
||||
else:
|
||||
# match for the method/function tag
|
||||
tagMatch = self.METHOD_RE.match(lineContent)
|
||||
|
||||
# if the method/function tag has been found, store some information on it {{{
|
||||
if (tagMatch):
|
||||
currentTag = self.getPythonTag(tagsStack, lineNumber, lineMatch.group(1),
|
||||
tagMatch.group(1), self.tagFunctionTypeDecidingMethod)
|
||||
tagLineNumbers.append(lineNumber)
|
||||
tags[lineNumber] = currentTag
|
||||
# }}}
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
# return the tags data for the source
|
||||
return (tagLineNumbers, tags,)
|
||||
# }}}
|
||||
|
||||
|
||||
def getParentTag(self, tagsStack):
|
||||
# DOC {{{
|
||||
"""Returns the parent/enclosing tag (instance of PythonTag()) from the
|
||||
specified tag list. If no such parent tag exists, returns None.
|
||||
|
||||
Parameters
|
||||
|
||||
tagsStack -- list (stack) of currently open PythonTag() instances
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# determine the parent tag {{{
|
||||
if (len(tagsStack)):
|
||||
parentTag = tagsStack[-1]
|
||||
else:
|
||||
parentTag = None
|
||||
# }}}
|
||||
|
||||
# return the tag
|
||||
return parentTag
|
||||
# }}}
|
||||
|
||||
|
||||
def computeIndentationLevel(indentChars):
|
||||
# DOC {{{
|
||||
"""Computes the indentation level from the specified string.
|
||||
|
||||
Parameters
|
||||
|
||||
indentChars -- white space before any other character on line
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# initialize the indentation level
|
||||
indentLevel = 0
|
||||
|
||||
# compute the indentation level (expand tabs) {{{
|
||||
for char in indentChars:
|
||||
if (char == '\t'):
|
||||
indentLevel += SimplePythonTagsParser.TABSIZE
|
||||
else:
|
||||
indentLevel += 1
|
||||
# }}}
|
||||
|
||||
# return the computed indentation level
|
||||
return indentLevel
|
||||
# }}}
|
||||
computeIndentationLevel = staticmethod(computeIndentationLevel)
|
||||
|
||||
|
||||
def getPythonTag(self, tagsStack, lineNumber, indentChars, tagName, tagTypeDecidingMethod):
|
||||
# DOC {{{
|
||||
"""Returns instance of PythonTag() based on the specified data.
|
||||
|
||||
Parameters
|
||||
|
||||
tagsStack -- list (stack) of tags currently active. Note: Modified
|
||||
in this method!
|
||||
|
||||
lineNumber -- current line number
|
||||
|
||||
indentChars -- characters making up the indentation level of the
|
||||
current tag
|
||||
|
||||
tagName -- short name of the current tag
|
||||
|
||||
tagTypeDecidingMethod -- reference to method that is called to
|
||||
determine the type of the current tag
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# compute the indentation level
|
||||
indentLevel = self.computeIndentationLevel(indentChars)
|
||||
# get the parent tag
|
||||
parentTag = self.getParentTag(tagsStack)
|
||||
|
||||
# handle an enclosed tag {{{
|
||||
while (parentTag):
|
||||
# if the indent level of the parent tag is greater than of the current tag, use parent tag of the parent tag {{{
|
||||
if (parentTag.indentLevel >= indentLevel):
|
||||
del tagsStack[-1]
|
||||
# }}}
|
||||
# otherwise we have all information on the current tag and can return it {{{
|
||||
else:
|
||||
# create the tag
|
||||
tag = PythonTag(tagTypeDecidingMethod(parentTag.type), tagName, "%s.%s" % (parentTag.fullName, tagName,), lineNumber, indentLevel)
|
||||
|
||||
# break the loop
|
||||
break
|
||||
# }}}
|
||||
|
||||
# use parent tag of the parent tag
|
||||
parentTag = self.getParentTag(tagsStack)
|
||||
# }}}
|
||||
# handle a top-indent level tag {{{
|
||||
else:
|
||||
# create the tag
|
||||
tag = PythonTag(tagTypeDecidingMethod(None), tagName, tagName, lineNumber, indentLevel)
|
||||
# }}}
|
||||
|
||||
# add the tag to the list of tags
|
||||
tagsStack.append(tag)
|
||||
|
||||
# return the tag
|
||||
return tag
|
||||
# }}}
|
||||
|
||||
|
||||
def tagClassTypeDecidingMethod(self, parentTagType):
|
||||
# DOC {{{
|
||||
"""Returns tag type of the current tag based on its previous tag (super
|
||||
tag) for classes.
|
||||
|
||||
Parameters
|
||||
|
||||
parentTagType -- type of the enclosing/parent tag
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# is always class no matter what
|
||||
return PythonTag.TT_CLASS
|
||||
# }}}
|
||||
|
||||
|
||||
def tagFunctionTypeDecidingMethod(self, parentTagType):
|
||||
# DOC {{{
|
||||
"""Returns tag type of the current tag based on its previous tag (super
|
||||
tag) for functions/methods.
|
||||
|
||||
Parameters
|
||||
|
||||
parentTagType -- type of the enclosing/parent tag
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
if (parentTagType == PythonTag.TT_CLASS):
|
||||
return PythonTag.TT_METHOD
|
||||
else:
|
||||
return PythonTag.TT_FUNCTION
|
||||
# }}}
|
||||
|
||||
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
|
||||
# class VimReadlineBuffer() {{{
|
||||
class VimReadlineBuffer(object):
|
||||
# DOC {{{
|
||||
"""A simple wrapper class around vim's buffer that provides readline
|
||||
method.
|
||||
"""
|
||||
# }}}
|
||||
|
||||
|
||||
# METHODS {{{
|
||||
|
||||
def __init__(self, vimBuffer):
|
||||
# DOC {{{
|
||||
"""Initializes instances of VimReadlineBuffer().
|
||||
|
||||
Parameters
|
||||
|
||||
vimBuffer -- VIM's buffer
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# remember the settings
|
||||
self.vimBuffer = vimBuffer
|
||||
|
||||
# initialize instance attributes {{{
|
||||
self.currentLine = -1
|
||||
self.bufferLines = len(vimBuffer)
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
|
||||
def readline(self):
|
||||
# DOC {{{
|
||||
"""Returns next line from the buffer. If all the buffer has been read,
|
||||
returns empty string.
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# increase the current line counter
|
||||
self.currentLine += 1
|
||||
|
||||
# notify end of file if we reached beyond the last line {{{
|
||||
if (self.currentLine == self.bufferLines):
|
||||
return ''
|
||||
# }}}
|
||||
|
||||
# return the line with an added newline (vim stores the lines without it)
|
||||
return "%s\n" % (self.vimBuffer[self.currentLine],)
|
||||
# }}}
|
||||
|
||||
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
|
||||
def getNearestLineIndex(row, tagLineNumbers):
|
||||
# DOC {{{
|
||||
"""Returns the index of line in 'tagLineNumbers' list that is nearest to the
|
||||
specified cursor row.
|
||||
|
||||
Parameters
|
||||
|
||||
row -- current cursor row
|
||||
|
||||
tagLineNumbers -- list of tags' line numbers (ie. their position)
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# initialize local auxiliary variables {{{
|
||||
nearestLineNumber = -1
|
||||
nearestLineIndex = -1
|
||||
# }}}
|
||||
|
||||
# go through all tag line numbers and find the one nearest to the specified row {{{
|
||||
for lineIndex, lineNumber in enumerate(tagLineNumbers):
|
||||
# if the current line is nearer the current cursor position, take it {{{
|
||||
if (nearestLineNumber < lineNumber <= row):
|
||||
nearestLineNumber = lineNumber
|
||||
nearestLineIndex = lineIndex
|
||||
# }}}
|
||||
|
||||
# if we've got past the current cursor position, let's end the search {{{
|
||||
if (lineNumber >= row):
|
||||
break
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
# return index of the line with the nearest tag
|
||||
return nearestLineIndex
|
||||
# }}}
|
||||
|
||||
|
||||
def getTags(bufferNumber, changedTick):
|
||||
# DOC {{{
|
||||
"""Reads the tags for the specified buffer number. Returns a tuple
|
||||
(taglinenumber[buffer], tags[buffer],).
|
||||
|
||||
Parameters
|
||||
|
||||
bufferNumber -- number of the current buffer
|
||||
|
||||
changedTick -- ever increasing number used to tell if the buffer has
|
||||
been modified since the last time
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# define global variables
|
||||
global TAGLINENUMBERS, TAGS, BUFFERTICKS
|
||||
|
||||
# return immediately if there's no need to update the tags {{{
|
||||
if (BUFFERTICKS.get(bufferNumber, None) == changedTick):
|
||||
return (TAGLINENUMBERS[bufferNumber], TAGS[bufferNumber],)
|
||||
# }}}
|
||||
|
||||
# get the tags {{{
|
||||
simpleTagsParser = SimplePythonTagsParser(VimReadlineBuffer(vim.current.buffer))
|
||||
tagLineNumbers, tags = simpleTagsParser.getTags()
|
||||
# }}}
|
||||
|
||||
# update the global variables {{{
|
||||
TAGS[bufferNumber] = tags
|
||||
TAGLINENUMBERS[bufferNumber] = tagLineNumbers
|
||||
BUFFERTICKS[bufferNumber] = changedTick
|
||||
# }}}
|
||||
|
||||
# return the tuple (tagLineNumbers, tags,)
|
||||
return (tagLineNumbers, tags,)
|
||||
# }}}
|
||||
|
||||
|
||||
def findTag(bufferNumber, changedTick):
|
||||
# DOC {{{
|
||||
"""Tries to find the best tag for the current cursor position.
|
||||
|
||||
Parameters
|
||||
|
||||
bufferNumber -- number of the current buffer
|
||||
|
||||
changedTick -- ever increasing number used to tell if the buffer has
|
||||
been modified since the last time
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# try to find the best tag {{{
|
||||
try:
|
||||
# get the tags data for the current buffer
|
||||
tagLineNumbers, tags = getTags(bufferNumber, changedTick)
|
||||
|
||||
# link to vim's internal data {{{
|
||||
currentBuffer = vim.current.buffer
|
||||
currentWindow = vim.current.window
|
||||
row, col = currentWindow.cursor
|
||||
# }}}
|
||||
|
||||
# get the index of the nearest line
|
||||
nearestLineIndex = getNearestLineIndex(row, tagLineNumbers)
|
||||
|
||||
# if any line was found, try to find if the tag is appropriate {{{
|
||||
# (ie. the cursor can be below the last tag but on a code that has nothing
|
||||
# to do with the tag, because it's indented differently, in such case no
|
||||
# appropriate tag has been found.)
|
||||
while (nearestLineIndex > -1):
|
||||
# get the line number of the nearest tag
|
||||
nearestLineNumber = tagLineNumbers[nearestLineIndex]
|
||||
|
||||
# walk through all the lines in range (nearestTagLine, cursorRow) {{{
|
||||
for lineNumber in xrange(nearestLineNumber + 1, row):
|
||||
# get the current line
|
||||
line = currentBuffer[lineNumber]
|
||||
|
||||
# count the indentation of the line, if it's lower than the tag's, the tag is invalid {{{
|
||||
if (len(line)):
|
||||
# initialize local auxiliary variables {{{
|
||||
lineStart = 0
|
||||
i = 0
|
||||
# }}}
|
||||
|
||||
# compute the indentation of the line {{{
|
||||
while ((i < len(line)) and (line[i].isspace())):
|
||||
# move the start of the line code {{{
|
||||
if (line[i] == '\t'):
|
||||
lineStart += SimplePythonTagsParser.TABSIZE
|
||||
else:
|
||||
lineStart += 1
|
||||
# }}}
|
||||
|
||||
# go to the next character on the line
|
||||
i += 1
|
||||
# }}}
|
||||
|
||||
# if the line contains only spaces, skip it {{{
|
||||
if (i == len(line)):
|
||||
continue
|
||||
# }}}
|
||||
# if the next character is a '#' (python comment), skip the line {{{
|
||||
if (line[i] == '#'):
|
||||
continue
|
||||
# }}}
|
||||
|
||||
# if the line's indentation starts before or at the nearest tag's one, the tag is invalid {{{
|
||||
if (lineStart <= tags[nearestLineNumber].indentLevel):
|
||||
nearestLineIndex -= 1
|
||||
break
|
||||
# }}}
|
||||
# }}}
|
||||
# }}}
|
||||
# the tag is appropriate, so use it {{{
|
||||
else:
|
||||
break
|
||||
# }}}
|
||||
# }}}
|
||||
# no appropriate tag has been found {{{
|
||||
else:
|
||||
nearestLineNumber = -1
|
||||
# }}}
|
||||
|
||||
# describe the cursor position (what tag the cursor is on) {{{
|
||||
# reset the description
|
||||
tagDescription = ""
|
||||
|
||||
# if an appropriate tag has been found, set the description accordingly {{{
|
||||
if (nearestLineNumber > -1):
|
||||
tagInfo = tags[nearestLineNumber]
|
||||
tagDescription = "[in %s (%s)]" % (tagInfo.fullName, PythonTag.TAG_TYPE_NAME[tagInfo.type],)
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
# update the variable for the status line so it get updated with the new description
|
||||
vim.command("let w:PHStatusLine=\"%s\"" % (tagDescription,))
|
||||
# }}}
|
||||
|
||||
# handle possible exceptions {{{
|
||||
except Exception:
|
||||
# bury into the traceback {{{
|
||||
ec, ei, tb = sys.exc_info()
|
||||
while (tb != None):
|
||||
if (tb.tb_next == None):
|
||||
break
|
||||
tb = tb.tb_next
|
||||
# }}}
|
||||
|
||||
# spit out the error {{{
|
||||
print "ERROR: %s %s %s:%u" % (ec.__name__, ei, tb.tb_frame.f_code.co_filename, tb.tb_lineno,)
|
||||
time.sleep(0.5)
|
||||
# }}}
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
|
||||
def deleteTags(bufferNumber):
|
||||
# DOC {{{
|
||||
"""Removes tags data for the specified buffer number.
|
||||
|
||||
Parameters
|
||||
|
||||
bufferNumber -- number of the buffer
|
||||
"""
|
||||
# }}}
|
||||
|
||||
# CODE {{{
|
||||
# define global variables
|
||||
global TAGS, TAGLINENUMBERS, BUFFERTICKS
|
||||
|
||||
# try to delete the tags for the buffer {{{
|
||||
try:
|
||||
del TAGS[bufferNumber]
|
||||
del TAGLINENUMBERS[bufferNumber]
|
||||
del BUFFERTICKS[bufferNumber]
|
||||
except:
|
||||
pass
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
|
||||
EOS
|
||||
|
||||
" VIM functions {{{
|
||||
|
||||
function! PHCursorHold()
|
||||
" only python is supported {{{
|
||||
if (!exists('b:current_syntax') || (b:current_syntax != 'python'))
|
||||
let w:PHStatusLine = ''
|
||||
return
|
||||
endif
|
||||
" }}}
|
||||
|
||||
" call python function findTag() with the current buffer number and changed ticks
|
||||
execute 'python findTag(' . expand("<abuf>") . ', ' . b:changedtick . ')'
|
||||
endfunction
|
||||
|
||||
|
||||
function! PHBufferDelete()
|
||||
" set PHStatusLine for this window to empty string
|
||||
let w:PHStatusLine = ""
|
||||
|
||||
" call python function deleteTags() with the cur
|
||||
execute 'python deleteTags(' . expand("<abuf>") . ')'
|
||||
endfunction
|
||||
|
||||
|
||||
function! TagInStatusLine()
|
||||
" return value of w:PHStatusLine in case it's set
|
||||
if (exists("w:PHStatusLine"))
|
||||
return w:PHStatusLine
|
||||
" otherwise just return empty string
|
||||
else
|
||||
return ""
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! PHPreviousClassMethod()
|
||||
call search('^[ \t]*\(class\|def\)\>', 'bw')
|
||||
endfunction
|
||||
|
||||
|
||||
function! PHNextClassMethod()
|
||||
call search('^[ \t]*\(class\|def\)\>', 'w')
|
||||
endfunction
|
||||
|
||||
|
||||
function! PHPreviousClass()
|
||||
call search('^[ \t]*class\>', 'bw')
|
||||
endfunction
|
||||
|
||||
|
||||
function! PHNextClass()
|
||||
call search('^[ \t]*class\>', 'w')
|
||||
endfunction
|
||||
|
||||
|
||||
function! PHPreviousMethod()
|
||||
call search('^[ \t]*def\>', 'bw')
|
||||
endfunction
|
||||
|
||||
|
||||
function! PHNextMethod()
|
||||
call search('^[ \t]*def\>', 'w')
|
||||
endfunction
|
||||
|
||||
" }}}
|
||||
|
||||
|
||||
" event binding, vim customizing {{{
|
||||
|
||||
" autocommands binding
|
||||
autocmd CursorHold * call PHCursorHold()
|
||||
autocmd CursorHoldI * call PHCursorHold()
|
||||
autocmd BufDelete * silent call PHBufferDelete()
|
||||
|
||||
" time that determines after how long time of no activity the CursorHold event
|
||||
" is fired up
|
||||
set updatetime=1000
|
||||
|
||||
" color of the current tag in the status line (bold cyan on black)
|
||||
" gryf: i don't like coloring apart from current colorscheme. keep it simple.
|
||||
"highlight User1 gui=bold guifg=cyan guibg=black
|
||||
" color of the modified flag in the status line (bold black on red)
|
||||
" gryf: i don't like coloring apart from current colorscheme. keep it simple.
|
||||
"highlight User2 gui=bold guifg=black guibg=red
|
||||
" the status line will be displayed for every window
|
||||
set laststatus=2
|
||||
" set the status line to display some useful information
|
||||
"set stl=%-f%r\ %2*%m%*\ \ \ \ %1*%{TagInStatusLine()}%*%=[%l:%c]\ \ \ \ [buf\ %n]
|
||||
" gryf: I like my status bar. Don't change it. Just add information.
|
||||
setlocal statusline=%<%F\ \ \ %{TagInStatusLine()}\ %h%m%r%=%(%l,%c%V%)\ %3p%%
|
||||
|
||||
" }}}
|
||||
|
||||
" vim:foldmethod=marker
|
||||
Reference in New Issue
Block a user