mirror of
https://github.com/gryf/.vim.git
synced 2025-12-17 19:40:29 +01:00
Update for plugins: pyflakes and pydoc. Added awesome plugin for autocompletion acp.
This commit is contained in:
@@ -8,7 +8,8 @@ ScriptID SourceID Filename
|
|||||||
2727 11120 jsbeautify.vim
|
2727 11120 jsbeautify.vim
|
||||||
2666 13424 Mark
|
2666 13424 Mark
|
||||||
2262 8944 occur.vim
|
2262 8944 occur.vim
|
||||||
910 14079 pydoc.vim
|
910 14349 pydoc.vim
|
||||||
|
1879 11894 AutoComplPop
|
||||||
#2421 9423 pysmell.vim
|
#2421 9423 pysmell.vim
|
||||||
152 3342 showmarks.vim
|
152 3342 showmarks.vim
|
||||||
2540 11006 snipMate.vim
|
2540 11006 snipMate.vim
|
||||||
@@ -27,7 +28,7 @@ ScriptID SourceID Filename
|
|||||||
# compiler
|
# compiler
|
||||||
891 10365 pylint.vim
|
891 10365 pylint.vim
|
||||||
# ftplugin
|
# ftplugin
|
||||||
2441 14215 pyflakes.vim
|
2441 14288 pyflakes.vim
|
||||||
30 9196 python_fn.vim
|
30 9196 python_fn.vim
|
||||||
### indent
|
### indent
|
||||||
1936 7708 javascript.vim
|
1936 7708 javascript.vim
|
||||||
|
|||||||
431
autoload/acp.vim
Normal file
431
autoload/acp.vim
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
"=============================================================================
|
||||||
|
" Copyright (c) 2007-2009 Takeshi NISHIDA
|
||||||
|
"
|
||||||
|
"=============================================================================
|
||||||
|
" LOAD GUARD {{{1
|
||||||
|
|
||||||
|
if exists('g:loaded_autoload_acp') || v:version < 702
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
let g:loaded_autoload_acp = 1
|
||||||
|
|
||||||
|
" }}}1
|
||||||
|
"=============================================================================
|
||||||
|
" GLOBAL FUNCTIONS: {{{1
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#enable()
|
||||||
|
call acp#disable()
|
||||||
|
|
||||||
|
augroup AcpGlobalAutoCommand
|
||||||
|
autocmd!
|
||||||
|
autocmd InsertEnter * unlet! s:posLast s:lastUncompletable
|
||||||
|
autocmd InsertLeave * call s:finishPopup(1)
|
||||||
|
augroup END
|
||||||
|
|
||||||
|
if g:acp_mappingDriven
|
||||||
|
call s:mapForMappingDriven()
|
||||||
|
else
|
||||||
|
autocmd AcpGlobalAutoCommand CursorMovedI * call s:feedPopup()
|
||||||
|
endif
|
||||||
|
|
||||||
|
nnoremap <silent> i i<C-r>=<SID>feedPopup()<CR>
|
||||||
|
nnoremap <silent> a a<C-r>=<SID>feedPopup()<CR>
|
||||||
|
nnoremap <silent> R R<C-r>=<SID>feedPopup()<CR>
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#disable()
|
||||||
|
call s:unmapForMappingDriven()
|
||||||
|
augroup AcpGlobalAutoCommand
|
||||||
|
autocmd!
|
||||||
|
augroup END
|
||||||
|
nnoremap i <Nop> | nunmap i
|
||||||
|
nnoremap a <Nop> | nunmap a
|
||||||
|
nnoremap R <Nop> | nunmap R
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#lock()
|
||||||
|
let s:lockCount += 1
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#unlock()
|
||||||
|
let s:lockCount -= 1
|
||||||
|
if s:lockCount < 0
|
||||||
|
let s:lockCount = 0
|
||||||
|
throw "AutoComplPop: not locked"
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#meetsForSnipmate(context)
|
||||||
|
if g:acp_behaviorSnipmateLength < 0
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
let matches = matchlist(a:context, '\(^\|\s\|\<\)\(\u\{' .
|
||||||
|
\ g:acp_behaviorSnipmateLength . ',}\)$')
|
||||||
|
return !empty(matches) && !empty(s:getMatchingSnipItems(matches[2]))
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#meetsForKeyword(context)
|
||||||
|
if g:acp_behaviorKeywordLength < 0
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
let matches = matchlist(a:context, '\(\k\{' . g:acp_behaviorKeywordLength . ',}\)$')
|
||||||
|
if empty(matches)
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
for ignore in g:acp_behaviorKeywordIgnores
|
||||||
|
if stridx(ignore, matches[1]) == 0
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
return 1
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#meetsForFile(context)
|
||||||
|
if g:acp_behaviorFileLength < 0
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
if has('win32') || has('win64')
|
||||||
|
let separator = '[/\\]'
|
||||||
|
else
|
||||||
|
let separator = '\/'
|
||||||
|
endif
|
||||||
|
if a:context !~ '\f' . separator . '\f\{' . g:acp_behaviorFileLength . ',}$'
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
return a:context !~ '[*/\\][/\\]\f*$\|[^[:print:]]\f*$'
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#meetsForRubyOmni(context)
|
||||||
|
if !has('ruby')
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
if g:acp_behaviorRubyOmniMethodLength >= 0 &&
|
||||||
|
\ a:context =~ '[^. \t]\(\.\|::\)\k\{' .
|
||||||
|
\ g:acp_behaviorRubyOmniMethodLength . ',}$'
|
||||||
|
return 1
|
||||||
|
endif
|
||||||
|
if g:acp_behaviorRubyOmniSymbolLength >= 0 &&
|
||||||
|
\ a:context =~ '\(^\|[^:]\):\k\{' .
|
||||||
|
\ g:acp_behaviorRubyOmniSymbolLength . ',}$'
|
||||||
|
return 1
|
||||||
|
endif
|
||||||
|
return 0
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#meetsForPythonOmni(context)
|
||||||
|
return has('python') && g:acp_behaviorPythonOmniLength >= 0 &&
|
||||||
|
\ a:context =~ '\k\.\k\{' . g:acp_behaviorPythonOmniLength . ',}$'
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#meetsForPerlOmni(context)
|
||||||
|
return g:acp_behaviorPerlOmniLength >= 0 &&
|
||||||
|
\ a:context =~ '\w->\k\{' . g:acp_behaviorPerlOmniLength . ',}$'
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#meetsForXmlOmni(context)
|
||||||
|
return g:acp_behaviorXmlOmniLength >= 0 &&
|
||||||
|
\ a:context =~ '\(<\|<\/\|<[^>]\+ \|<[^>]\+=\"\)\k\{' .
|
||||||
|
\ g:acp_behaviorXmlOmniLength . ',}$'
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#meetsForHtmlOmni(context)
|
||||||
|
return g:acp_behaviorHtmlOmniLength >= 0 &&
|
||||||
|
\ a:context =~ '\(<\|<\/\|<[^>]\+ \|<[^>]\+=\"\)\k\{' .
|
||||||
|
\ g:acp_behaviorHtmlOmniLength . ',}$'
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#meetsForCssOmni(context)
|
||||||
|
if g:acp_behaviorCssOmniPropertyLength >= 0 &&
|
||||||
|
\ a:context =~ '\(^\s\|[;{]\)\s*\k\{' .
|
||||||
|
\ g:acp_behaviorCssOmniPropertyLength . ',}$'
|
||||||
|
return 1
|
||||||
|
endif
|
||||||
|
if g:acp_behaviorCssOmniValueLength >= 0 &&
|
||||||
|
\ a:context =~ '[:@!]\s*\k\{' .
|
||||||
|
\ g:acp_behaviorCssOmniValueLength . ',}$'
|
||||||
|
return 1
|
||||||
|
endif
|
||||||
|
return 0
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#completeSnipmate(findstart, base)
|
||||||
|
if a:findstart
|
||||||
|
let s:posSnipmateCompletion = len(matchstr(s:getCurrentText(), '.*\U'))
|
||||||
|
return s:posSnipmateCompletion
|
||||||
|
endif
|
||||||
|
let lenBase = len(a:base)
|
||||||
|
let items = filter(GetSnipsInCurrentScope(),
|
||||||
|
\ 'strpart(v:key, 0, lenBase) ==? a:base')
|
||||||
|
return map(sort(items(items)), 's:makeSnipmateItem(v:val[0], v:val[1])')
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#onPopupCloseSnipmate()
|
||||||
|
let word = s:getCurrentText()[s:posSnipmateCompletion :]
|
||||||
|
for trigger in keys(GetSnipsInCurrentScope())
|
||||||
|
if word ==# trigger
|
||||||
|
call feedkeys("\<C-r>=TriggerSnippet()\<CR>", "n")
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
return 1
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#onPopupPost()
|
||||||
|
" to clear <C-r>= expression on command-line
|
||||||
|
echo ''
|
||||||
|
if pumvisible()
|
||||||
|
inoremap <silent> <expr> <C-h> acp#onBs()
|
||||||
|
inoremap <silent> <expr> <BS> acp#onBs()
|
||||||
|
" a command to restore to original text and select the first match
|
||||||
|
return (s:behavsCurrent[s:iBehavs].command =~# "\<C-p>" ? "\<C-n>\<Up>"
|
||||||
|
\ : "\<C-p>\<Down>")
|
||||||
|
endif
|
||||||
|
let s:iBehavs += 1
|
||||||
|
if len(s:behavsCurrent) > s:iBehavs
|
||||||
|
call s:setCompletefunc()
|
||||||
|
return printf("\<C-e>%s\<C-r>=acp#onPopupPost()\<CR>",
|
||||||
|
\ s:behavsCurrent[s:iBehavs].command)
|
||||||
|
else
|
||||||
|
let s:lastUncompletable = {
|
||||||
|
\ 'word': s:getCurrentWord(),
|
||||||
|
\ 'commands': map(copy(s:behavsCurrent), 'v:val.command')[1:],
|
||||||
|
\ }
|
||||||
|
call s:finishPopup(0)
|
||||||
|
return "\<C-e>"
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function acp#onBs()
|
||||||
|
" using "matchstr" and not "strpart" in order to handle multi-byte
|
||||||
|
" characters
|
||||||
|
if call(s:behavsCurrent[s:iBehavs].meets,
|
||||||
|
\ [matchstr(s:getCurrentText(), '.*\ze.')])
|
||||||
|
return "\<BS>"
|
||||||
|
endif
|
||||||
|
return "\<C-e>\<BS>"
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" }}}1
|
||||||
|
"=============================================================================
|
||||||
|
" LOCAL FUNCTIONS: {{{1
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:mapForMappingDriven()
|
||||||
|
call s:unmapForMappingDriven()
|
||||||
|
let s:keysMappingDriven = [
|
||||||
|
\ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||||
|
\ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||||
|
\ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||||
|
\ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||||
|
\ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
\ '-', '_', '~', '^', '.', ',', ':', '!', '#', '=', '%', '$', '@', '<', '>', '/', '\',
|
||||||
|
\ '<Space>', '<C-h>', '<BS>', ]
|
||||||
|
for key in s:keysMappingDriven
|
||||||
|
execute printf('inoremap <silent> %s %s<C-r>=<SID>feedPopup()<CR>',
|
||||||
|
\ key, key)
|
||||||
|
endfor
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:unmapForMappingDriven()
|
||||||
|
if !exists('s:keysMappingDriven')
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
for key in s:keysMappingDriven
|
||||||
|
execute 'iunmap ' . key
|
||||||
|
endfor
|
||||||
|
let s:keysMappingDriven = []
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:setTempOption(group, name, value)
|
||||||
|
call extend(s:tempOptionSet[a:group], { a:name : eval('&' . a:name) }, 'keep')
|
||||||
|
execute printf('let &%s = a:value', a:name)
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:restoreTempOptions(group)
|
||||||
|
for [name, value] in items(s:tempOptionSet[a:group])
|
||||||
|
execute printf('let &%s = value', name)
|
||||||
|
endfor
|
||||||
|
let s:tempOptionSet[a:group] = {}
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:getCurrentWord()
|
||||||
|
return matchstr(s:getCurrentText(), '\k*$')
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:getCurrentText()
|
||||||
|
return strpart(getline('.'), 0, col('.') - 1)
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:getPostText()
|
||||||
|
return strpart(getline('.'), col('.') - 1)
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:isModifiedSinceLastCall()
|
||||||
|
if exists('s:posLast')
|
||||||
|
let posPrev = s:posLast
|
||||||
|
let nLinesPrev = s:nLinesLast
|
||||||
|
let textPrev = s:textLast
|
||||||
|
endif
|
||||||
|
let s:posLast = getpos('.')
|
||||||
|
let s:nLinesLast = line('$')
|
||||||
|
let s:textLast = getline('.')
|
||||||
|
if !exists('posPrev')
|
||||||
|
return 1
|
||||||
|
elseif posPrev[1] != s:posLast[1] || nLinesPrev != s:nLinesLast
|
||||||
|
return (posPrev[1] - s:posLast[1] == nLinesPrev - s:nLinesLast)
|
||||||
|
elseif textPrev ==# s:textLast
|
||||||
|
return 0
|
||||||
|
elseif posPrev[2] > s:posLast[2]
|
||||||
|
return 1
|
||||||
|
elseif has('gui_running') && has('multi_byte')
|
||||||
|
" NOTE: auto-popup causes a strange behavior when IME/XIM is working
|
||||||
|
return posPrev[2] + 1 == s:posLast[2]
|
||||||
|
endif
|
||||||
|
return posPrev[2] != s:posLast[2]
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:makeCurrentBehaviorSet()
|
||||||
|
let modified = s:isModifiedSinceLastCall()
|
||||||
|
if exists('s:behavsCurrent[s:iBehavs].repeat') && s:behavsCurrent[s:iBehavs].repeat
|
||||||
|
let behavs = [ s:behavsCurrent[s:iBehavs] ]
|
||||||
|
elseif exists('s:behavsCurrent[s:iBehavs]')
|
||||||
|
return []
|
||||||
|
elseif modified
|
||||||
|
let behavs = copy(exists('g:acp_behavior[&filetype]')
|
||||||
|
\ ? g:acp_behavior[&filetype]
|
||||||
|
\ : g:acp_behavior['*'])
|
||||||
|
else
|
||||||
|
return []
|
||||||
|
endif
|
||||||
|
let text = s:getCurrentText()
|
||||||
|
call filter(behavs, 'call(v:val.meets, [text])')
|
||||||
|
let s:iBehavs = 0
|
||||||
|
if exists('s:lastUncompletable') &&
|
||||||
|
\ stridx(s:getCurrentWord(), s:lastUncompletable.word) == 0 &&
|
||||||
|
\ map(copy(behavs), 'v:val.command') ==# s:lastUncompletable.commands
|
||||||
|
let behavs = []
|
||||||
|
else
|
||||||
|
unlet! s:lastUncompletable
|
||||||
|
endif
|
||||||
|
return behavs
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:feedPopup()
|
||||||
|
" NOTE: CursorMovedI is not triggered while the popup menu is visible. And
|
||||||
|
" it will be triggered when popup menu is disappeared.
|
||||||
|
if s:lockCount > 0 || pumvisible() || &paste
|
||||||
|
return ''
|
||||||
|
endif
|
||||||
|
if exists('s:behavsCurrent[s:iBehavs].onPopupClose')
|
||||||
|
if !call(s:behavsCurrent[s:iBehavs].onPopupClose, [])
|
||||||
|
call s:finishPopup(1)
|
||||||
|
return ''
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
let s:behavsCurrent = s:makeCurrentBehaviorSet()
|
||||||
|
if empty(s:behavsCurrent)
|
||||||
|
call s:finishPopup(1)
|
||||||
|
return ''
|
||||||
|
endif
|
||||||
|
" In case of dividing words by symbols (e.g. "for(int", "ab==cd") while a
|
||||||
|
" popup menu is visible, another popup is not available unless input <C-e>
|
||||||
|
" or try popup once. So first completion is duplicated.
|
||||||
|
call insert(s:behavsCurrent, s:behavsCurrent[s:iBehavs])
|
||||||
|
call s:setTempOption(s:GROUP0, 'spell', 0)
|
||||||
|
call s:setTempOption(s:GROUP0, 'completeopt', 'menuone' . (g:acp_completeoptPreview ? ',preview' : ''))
|
||||||
|
call s:setTempOption(s:GROUP0, 'complete', g:acp_completeOption)
|
||||||
|
call s:setTempOption(s:GROUP0, 'ignorecase', g:acp_ignorecaseOption)
|
||||||
|
" NOTE: With CursorMovedI driven, Set 'lazyredraw' to avoid flickering.
|
||||||
|
" With Mapping driven, set 'nolazyredraw' to make a popup menu visible.
|
||||||
|
call s:setTempOption(s:GROUP0, 'lazyredraw', !g:acp_mappingDriven)
|
||||||
|
" NOTE: 'textwidth' must be restored after <C-e>.
|
||||||
|
call s:setTempOption(s:GROUP1, 'textwidth', 0)
|
||||||
|
call s:setCompletefunc()
|
||||||
|
call feedkeys(s:behavsCurrent[s:iBehavs].command . "\<C-r>=acp#onPopupPost()\<CR>", 'n')
|
||||||
|
return '' " this function is called by <C-r>=
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:finishPopup(fGroup1)
|
||||||
|
inoremap <C-h> <Nop> | iunmap <C-h>
|
||||||
|
inoremap <BS> <Nop> | iunmap <BS>
|
||||||
|
let s:behavsCurrent = []
|
||||||
|
call s:restoreTempOptions(s:GROUP0)
|
||||||
|
if a:fGroup1
|
||||||
|
call s:restoreTempOptions(s:GROUP1)
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:setCompletefunc()
|
||||||
|
if exists('s:behavsCurrent[s:iBehavs].completefunc')
|
||||||
|
call s:setTempOption(0, 'completefunc', s:behavsCurrent[s:iBehavs].completefunc)
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:makeSnipmateItem(key, snip)
|
||||||
|
if type(a:snip) == type([])
|
||||||
|
let descriptions = map(copy(a:snip), 'v:val[0]')
|
||||||
|
let snipFormatted = '[MULTI] ' . join(descriptions, ', ')
|
||||||
|
else
|
||||||
|
let snipFormatted = substitute(a:snip, '\(\n\|\s\)\+', ' ', 'g')
|
||||||
|
endif
|
||||||
|
return {
|
||||||
|
\ 'word': a:key,
|
||||||
|
\ 'menu': strpart(snipFormatted, 0, 80),
|
||||||
|
\ }
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:getMatchingSnipItems(base)
|
||||||
|
let key = a:base . "\n"
|
||||||
|
if !exists('s:snipItems[key]')
|
||||||
|
let s:snipItems[key] = items(GetSnipsInCurrentScope())
|
||||||
|
call filter(s:snipItems[key], 'strpart(v:val[0], 0, len(a:base)) ==? a:base')
|
||||||
|
call map(s:snipItems[key], 's:makeSnipmateItem(v:val[0], v:val[1])')
|
||||||
|
endif
|
||||||
|
return s:snipItems[key]
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" }}}1
|
||||||
|
"=============================================================================
|
||||||
|
" INITIALIZATION {{{1
|
||||||
|
|
||||||
|
let s:GROUP0 = 0
|
||||||
|
let s:GROUP1 = 1
|
||||||
|
let s:lockCount = 0
|
||||||
|
let s:behavsCurrent = []
|
||||||
|
let s:iBehavs = 0
|
||||||
|
let s:tempOptionSet = [{}, {}]
|
||||||
|
let s:snipItems = {}
|
||||||
|
|
||||||
|
" }}}1
|
||||||
|
"=============================================================================
|
||||||
|
" vim: set fdm=marker:
|
||||||
512
doc/acp.txt
Normal file
512
doc/acp.txt
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
*acp.txt* Automatically opens popup menu for completions.
|
||||||
|
|
||||||
|
Copyright (c) 2007-2009 Takeshi NISHIDA
|
||||||
|
|
||||||
|
AutoComplPop *autocomplpop* *acp*
|
||||||
|
|
||||||
|
INTRODUCTION |acp-introduction|
|
||||||
|
INSTALLATION |acp-installation|
|
||||||
|
USAGE |acp-usage|
|
||||||
|
COMMANDS |acp-commands|
|
||||||
|
OPTIONS |acp-options|
|
||||||
|
SPECIAL THANKS |acp-thanks|
|
||||||
|
CHANGELOG |acp-changelog|
|
||||||
|
ABOUT |acp-about|
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
INTRODUCTION *acp-introduction*
|
||||||
|
|
||||||
|
With this plugin, your vim comes to automatically opens popup menu for
|
||||||
|
completions when you enter characters or move the cursor in Insert mode. It
|
||||||
|
won't prevent you continuing entering characters.
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
INSTALLATION *acp-installation*
|
||||||
|
|
||||||
|
Put all files into your runtime directory. If you have the zip file, extract
|
||||||
|
it to your runtime directory.
|
||||||
|
|
||||||
|
You should place the files as follows:
|
||||||
|
>
|
||||||
|
<your runtime directory>/plugin/acp.vim
|
||||||
|
<your runtime directory>/doc/acp.txt
|
||||||
|
...
|
||||||
|
<
|
||||||
|
If you disgust to jumble up this plugin and other plugins in your runtime
|
||||||
|
directory, put the files into new directory and just add the directory path to
|
||||||
|
'runtimepath'. It's easy to uninstall the plugin.
|
||||||
|
|
||||||
|
And then update your help tags files to enable fuzzyfinder help. See
|
||||||
|
|add-local-help| for details.
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
USAGE *acp-usage*
|
||||||
|
|
||||||
|
Once this plugin is installed, auto-popup is enabled at startup by default.
|
||||||
|
|
||||||
|
Which completion method is used depends on the text before the cursor. The
|
||||||
|
default behavior is as follows:
|
||||||
|
|
||||||
|
kind filetype text before the cursor ~
|
||||||
|
Keyword * two keyword characters
|
||||||
|
Filename * a filename character + a path separator
|
||||||
|
+ 0 or more filename character
|
||||||
|
Omni ruby ".", "::" or non-word character + ":"
|
||||||
|
(|+ruby| required.)
|
||||||
|
Omni python "." (|+python| required.)
|
||||||
|
Omni xml "<", "</" or ("<" + non-">" characters + " ")
|
||||||
|
Omni html/xhtml "<", "</" or ("<" + non-">" characters + " ")
|
||||||
|
Omni css (":", ";", "{", "^", "@", or "!")
|
||||||
|
+ 0 or 1 space
|
||||||
|
|
||||||
|
Also, you can make user-defined completion and snipMate's trigger completion
|
||||||
|
(|acp-snipMate|) auto-popup if the options are set.
|
||||||
|
|
||||||
|
These behavior are customizable.
|
||||||
|
|
||||||
|
*acp-snipMate*
|
||||||
|
snipMate's Trigger Completion ~
|
||||||
|
|
||||||
|
snipMate's trigger completion enables you to complete a snippet trigger
|
||||||
|
provided by snipMate plugin
|
||||||
|
(http://www.vim.org/scripts/script.php?script_id=2540) and expand it.
|
||||||
|
|
||||||
|
|
||||||
|
To enable auto-popup for this completion, add following function to
|
||||||
|
plugin/snipMate.vim:
|
||||||
|
>
|
||||||
|
fun! GetSnipsInCurrentScope()
|
||||||
|
let snips = {}
|
||||||
|
for scope in [bufnr('%')] + split(&ft, '\.') + ['_']
|
||||||
|
call extend(snips, get(s:snippets, scope, {}), 'keep')
|
||||||
|
call extend(snips, get(s:multi_snips, scope, {}), 'keep')
|
||||||
|
endfor
|
||||||
|
return snips
|
||||||
|
endf
|
||||||
|
<
|
||||||
|
And set |g:acp_behaviorSnipmateLength| option to 1.
|
||||||
|
|
||||||
|
There is the restriction on this auto-popup, that the word before cursor must
|
||||||
|
consist only of uppercase characters.
|
||||||
|
|
||||||
|
*acp-perl-omni*
|
||||||
|
Perl Omni-Completion ~
|
||||||
|
|
||||||
|
AutoComplPop supports perl-completion.vim
|
||||||
|
(http://www.vim.org/scripts/script.php?script_id=2852).
|
||||||
|
|
||||||
|
To enable auto-popup for this completion, set |g:acp_behaviorPerlOmniLength|
|
||||||
|
option to 0 or more.
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
COMMANDS *acp-commands*
|
||||||
|
|
||||||
|
*:AcpEnable*
|
||||||
|
:AcpEnable
|
||||||
|
enables auto-popup.
|
||||||
|
|
||||||
|
*:AcpDisable*
|
||||||
|
:AcpDisable
|
||||||
|
disables auto-popup.
|
||||||
|
|
||||||
|
*:AcpLock*
|
||||||
|
:AcpLock
|
||||||
|
suspends auto-popup temporarily.
|
||||||
|
|
||||||
|
For the purpose of avoiding interruption to another script, it is
|
||||||
|
recommended to insert this command and |:AcpUnlock| than |:AcpDisable|
|
||||||
|
and |:AcpEnable| .
|
||||||
|
|
||||||
|
*:AcpUnlock*
|
||||||
|
:AcpUnlock
|
||||||
|
resumes auto-popup suspended by |:AcpLock| .
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
OPTIONS *acp-options*
|
||||||
|
|
||||||
|
*g:acp_enableAtStartup* >
|
||||||
|
let g:acp_enableAtStartup = 1
|
||||||
|
<
|
||||||
|
If non-zero, auto-popup is enabled at startup.
|
||||||
|
|
||||||
|
*g:acp_mappingDriven* >
|
||||||
|
let g:acp_mappingDriven = 0
|
||||||
|
<
|
||||||
|
If non-zero, auto-popup is triggered by key mappings instead of
|
||||||
|
|CursorMovedI| event. This is useful to avoid auto-popup by moving
|
||||||
|
cursor in Insert mode.
|
||||||
|
|
||||||
|
*g:acp_ignorecaseOption* >
|
||||||
|
let g:acp_ignorecaseOption = 1
|
||||||
|
<
|
||||||
|
Value set to 'ignorecase' temporarily when auto-popup.
|
||||||
|
|
||||||
|
*g:acp_completeOption* >
|
||||||
|
let g:acp_completeOption = '.,w,b,k'
|
||||||
|
<
|
||||||
|
Value set to 'complete' temporarily when auto-popup.
|
||||||
|
|
||||||
|
*g:acp_completeoptPreview* >
|
||||||
|
let g:acp_completeoptPreview = 0
|
||||||
|
<
|
||||||
|
If non-zero, "preview" is added to 'completeopt' when auto-popup.
|
||||||
|
|
||||||
|
*g:acp_behaviorUserDefinedFunction* >
|
||||||
|
let g:acp_behaviorUserDefinedFunction = ''
|
||||||
|
<
|
||||||
|
|g:acp_behavior-completefunc| for user-defined completion. If empty,
|
||||||
|
this completion will be never attempted.
|
||||||
|
|
||||||
|
*g:acp_behaviorUserDefinedMeets* >
|
||||||
|
let g:acp_behaviorUserDefinedMeets = ''
|
||||||
|
<
|
||||||
|
|g:acp_behavior-meets| for user-defined completion. If empty, this
|
||||||
|
completion will be never attempted.
|
||||||
|
|
||||||
|
*g:acp_behaviorSnipmateLength* >
|
||||||
|
let g:acp_behaviorSnipmateLength = -1
|
||||||
|
<
|
||||||
|
Pattern before the cursor, which are needed to attempt
|
||||||
|
snipMate-trigger completion.
|
||||||
|
|
||||||
|
*g:acp_behaviorKeywordCommand* >
|
||||||
|
let g:acp_behaviorKeywordCommand = "\<C-n>"
|
||||||
|
<
|
||||||
|
Command for keyword completion. This option is usually set "\<C-n>" or
|
||||||
|
"\<C-p>".
|
||||||
|
|
||||||
|
*g:acp_behaviorKeywordLength* >
|
||||||
|
let g:acp_behaviorKeywordLength = 2
|
||||||
|
<
|
||||||
|
Length of keyword characters before the cursor, which are needed to
|
||||||
|
attempt keyword completion. If negative value, this completion will be
|
||||||
|
never attempted.
|
||||||
|
|
||||||
|
*g:acp_behaviorKeywordIgnores* >
|
||||||
|
let g:acp_behaviorKeywordIgnores = []
|
||||||
|
<
|
||||||
|
List of string. If a word before the cursor matches to the front part
|
||||||
|
of one of them, keyword completion won't be attempted.
|
||||||
|
|
||||||
|
E.g., when there are too many keywords beginning with "get" for the
|
||||||
|
completion and auto-popup by entering "g", "ge", or "get" causes
|
||||||
|
response degradation, set ["get"] to this option and avoid it.
|
||||||
|
|
||||||
|
*g:acp_behaviorFileLength* >
|
||||||
|
let g:acp_behaviorFileLength = 0
|
||||||
|
<
|
||||||
|
Length of filename characters before the cursor, which are needed to
|
||||||
|
attempt filename completion. If negative value, this completion will
|
||||||
|
be never attempted.
|
||||||
|
|
||||||
|
*g:acp_behaviorRubyOmniMethodLength* >
|
||||||
|
let g:acp_behaviorRubyOmniMethodLength = 0
|
||||||
|
<
|
||||||
|
Length of keyword characters before the cursor, which are needed to
|
||||||
|
attempt ruby omni-completion for methods. If negative value, this
|
||||||
|
completion will be never attempted.
|
||||||
|
|
||||||
|
*g:acp_behaviorRubyOmniSymbolLength* >
|
||||||
|
let g:acp_behaviorRubyOmniSymbolLength = 1
|
||||||
|
<
|
||||||
|
Length of keyword characters before the cursor, which are needed to
|
||||||
|
attempt ruby omni-completion for symbols. If negative value, this
|
||||||
|
completion will be never attempted.
|
||||||
|
|
||||||
|
*g:acp_behaviorPythonOmniLength* >
|
||||||
|
let g:acp_behaviorPythonOmniLength = 0
|
||||||
|
<
|
||||||
|
Length of keyword characters before the cursor, which are needed to
|
||||||
|
attempt python omni-completion. If negative value, this completion
|
||||||
|
will be never attempted.
|
||||||
|
|
||||||
|
*g:acp_behaviorPerlOmniLength* >
|
||||||
|
let g:acp_behaviorPerlOmniLength = -1
|
||||||
|
<
|
||||||
|
Length of keyword characters before the cursor, which are needed to
|
||||||
|
attempt perl omni-completion. If negative value, this completion will
|
||||||
|
be never attempted.
|
||||||
|
|
||||||
|
See also: |acp-perl-omni|
|
||||||
|
|
||||||
|
*g:acp_behaviorXmlOmniLength* >
|
||||||
|
let g:acp_behaviorXmlOmniLength = 0
|
||||||
|
<
|
||||||
|
Length of keyword characters before the cursor, which are needed to
|
||||||
|
attempt XML omni-completion. If negative value, this completion will
|
||||||
|
be never attempted.
|
||||||
|
|
||||||
|
*g:acp_behaviorHtmlOmniLength* >
|
||||||
|
let g:acp_behaviorHtmlOmniLength = 0
|
||||||
|
<
|
||||||
|
Length of keyword characters before the cursor, which are needed to
|
||||||
|
attempt HTML omni-completion. If negative value, this completion will
|
||||||
|
be never attempted.
|
||||||
|
|
||||||
|
*g:acp_behaviorCssOmniPropertyLength* >
|
||||||
|
let g:acp_behaviorCssOmniPropertyLength = 1
|
||||||
|
<
|
||||||
|
Length of keyword characters before the cursor, which are needed to
|
||||||
|
attempt CSS omni-completion for properties. If negative value, this
|
||||||
|
completion will be never attempted.
|
||||||
|
|
||||||
|
*g:acp_behaviorCssOmniValueLength* >
|
||||||
|
let g:acp_behaviorCssOmniValueLength = 0
|
||||||
|
<
|
||||||
|
Length of keyword characters before the cursor, which are needed to
|
||||||
|
attempt CSS omni-completion for values. If negative value, this
|
||||||
|
completion will be never attempted.
|
||||||
|
|
||||||
|
*g:acp_behavior* >
|
||||||
|
let g:acp_behavior = {}
|
||||||
|
<
|
||||||
|
This option is for advanced users. This setting overrides other
|
||||||
|
behavior options. This is a |Dictionary|. Each key corresponds to a
|
||||||
|
filetype. '*' is default. Each value is a list. These are attempted in
|
||||||
|
sequence until completion item is found. Each element is a
|
||||||
|
|Dictionary| which has following items:
|
||||||
|
|
||||||
|
"command": *g:acp_behavior-command*
|
||||||
|
Command to be fed to open popup menu for completions.
|
||||||
|
|
||||||
|
"completefunc": *g:acp_behavior-completefunc*
|
||||||
|
'completefunc' will be set to this user-provided function during the
|
||||||
|
completion. Only makes sense when "command" is "<C-x><C-u>".
|
||||||
|
|
||||||
|
"meets": *g:acp_behavior-meets*
|
||||||
|
Name of the function which dicides whether or not to attempt this
|
||||||
|
completion. It will be attempted if this function returns non-zero.
|
||||||
|
This function takes a text before the cursor.
|
||||||
|
|
||||||
|
"onPopupClose": *g:acp_behavior-onPopupClose*
|
||||||
|
Name of the function which is called when popup menu for this
|
||||||
|
completion is closed. Following completions will be suppressed if
|
||||||
|
this function returns zero.
|
||||||
|
|
||||||
|
"repeat": *g:acp_behavior-repeat*
|
||||||
|
If non-zero, the last completion is automatically repeated.
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
SPECIAL THANKS *acp-thanks*
|
||||||
|
|
||||||
|
- Daniel Schierbeck
|
||||||
|
- Ingo Karkat
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
CHANGELOG *acp-changelog*
|
||||||
|
|
||||||
|
2.14.1
|
||||||
|
- Changed the way of auto-popup for avoiding an issue about filename
|
||||||
|
completion.
|
||||||
|
- Fixed a bug that popup menu was opened twice when auto-popup was done.
|
||||||
|
|
||||||
|
2.14
|
||||||
|
- Added the support for perl-completion.vim.
|
||||||
|
|
||||||
|
2.13
|
||||||
|
- Changed to sort snipMate's triggers.
|
||||||
|
- Fixed a bug that a wasted character was inserted after snipMate's trigger
|
||||||
|
completion.
|
||||||
|
|
||||||
|
2.12.1
|
||||||
|
- Changed to avoid a strange behavior with Microsoft IME.
|
||||||
|
|
||||||
|
2.12
|
||||||
|
- Added g:acp_behaviorKeywordIgnores option.
|
||||||
|
- Added g:acp_behaviorUserDefinedMeets option and removed
|
||||||
|
g:acp_behaviorUserDefinedPattern.
|
||||||
|
- Changed to do auto-popup only when a buffer is modified.
|
||||||
|
- Changed the structure of g:acp_behavior option.
|
||||||
|
- Changed to reflect a change of behavior options (named g:acp_behavior*)
|
||||||
|
any time it is done.
|
||||||
|
- Fixed a bug that completions after omni completions or snipMate's trigger
|
||||||
|
completion were never attempted when no candidate for the former
|
||||||
|
completions was found.
|
||||||
|
|
||||||
|
2.11.1
|
||||||
|
- Fixed a bug that a snipMate's trigger could not be expanded when it was
|
||||||
|
completed.
|
||||||
|
|
||||||
|
2.11
|
||||||
|
- Implemented experimental feature which is snipMate's trigger completion.
|
||||||
|
|
||||||
|
2.10
|
||||||
|
- Improved the response by changing not to attempt any completion when
|
||||||
|
keyword characters are entered after a word which has been found that it
|
||||||
|
has no completion candidate at the last attempt of completions.
|
||||||
|
- Improved the response by changing to close popup menu when <BS> was
|
||||||
|
pressed and the text before the cursor would not match with the pattern of
|
||||||
|
current behavior.
|
||||||
|
|
||||||
|
2.9
|
||||||
|
- Changed default behavior to support XML omni completion.
|
||||||
|
- Changed default value of g:acp_behaviorKeywordCommand option.
|
||||||
|
The option with "\<C-p>" cause a problem which inserts a match without
|
||||||
|
<CR> when 'dictionary' has been set and keyword completion is done.
|
||||||
|
- Changed to show error message when incompatible with a installed vim.
|
||||||
|
|
||||||
|
2.8.1
|
||||||
|
- Fixed a bug which inserted a selected match to the next line when
|
||||||
|
auto-wrapping (enabled with 'formatoptions') was performed.
|
||||||
|
|
||||||
|
2.8
|
||||||
|
- Added g:acp_behaviorUserDefinedFunction option and
|
||||||
|
g:acp_behaviorUserDefinedPattern option for users who want to make custom
|
||||||
|
completion auto-popup.
|
||||||
|
- Fixed a bug that setting 'spell' on a new buffer made typing go crazy.
|
||||||
|
|
||||||
|
2.7
|
||||||
|
- Changed naming conventions for filenames, functions, commands, and options
|
||||||
|
and thus renamed them.
|
||||||
|
- Added g:acp_behaviorKeywordCommand option. If you prefer the previous
|
||||||
|
behavior for keyword completion, set this option "\<C-n>".
|
||||||
|
- Changed default value of g:acp_ignorecaseOption option.
|
||||||
|
|
||||||
|
The following were done by Ingo Karkat:
|
||||||
|
|
||||||
|
- ENH: Added support for setting a user-provided 'completefunc' during the
|
||||||
|
completion, configurable via g:acp_behavior.
|
||||||
|
- BUG: When the configured completion is <C-p> or <C-x><C-p>, the command to
|
||||||
|
restore the original text (in on_popup_post()) must be reverted, too.
|
||||||
|
- BUG: When using a custom completion function (<C-x><C-u>) that also uses
|
||||||
|
an s:...() function name, the s:GetSidPrefix() function dynamically
|
||||||
|
determines the wrong SID. Now calling s:DetermineSidPrefix() once during
|
||||||
|
sourcing and caching the value in s:SID.
|
||||||
|
- BUG: Should not use custom defined <C-X><C-...> completion mappings. Now
|
||||||
|
consistently using unmapped completion commands everywhere. (Beforehand,
|
||||||
|
s:PopupFeeder.feed() used mappings via feedkeys(..., 'm'), but
|
||||||
|
s:PopupFeeder.on_popup_post() did not due to its invocation via
|
||||||
|
:map-expr.)
|
||||||
|
|
||||||
|
2.6:
|
||||||
|
- Improved the behavior of omni completion for HTML/XHTML.
|
||||||
|
|
||||||
|
2.5:
|
||||||
|
- Added some options to customize behavior easily:
|
||||||
|
g:AutoComplPop_BehaviorKeywordLength
|
||||||
|
g:AutoComplPop_BehaviorFileLength
|
||||||
|
g:AutoComplPop_BehaviorRubyOmniMethodLength
|
||||||
|
g:AutoComplPop_BehaviorRubyOmniSymbolLength
|
||||||
|
g:AutoComplPop_BehaviorPythonOmniLength
|
||||||
|
g:AutoComplPop_BehaviorHtmlOmniLength
|
||||||
|
g:AutoComplPop_BehaviorCssOmniPropertyLength
|
||||||
|
g:AutoComplPop_BehaviorCssOmniValueLength
|
||||||
|
|
||||||
|
2.4:
|
||||||
|
- Added g:AutoComplPop_MappingDriven option.
|
||||||
|
|
||||||
|
2.3.1:
|
||||||
|
- Changed to set 'lazyredraw' while a popup menu is visible to avoid
|
||||||
|
flickering.
|
||||||
|
- Changed a behavior for CSS.
|
||||||
|
- Added support for GetLatestVimScripts.
|
||||||
|
|
||||||
|
2.3:
|
||||||
|
- Added a behavior for Python to support omni completion.
|
||||||
|
- Added a behavior for CSS to support omni completion.
|
||||||
|
|
||||||
|
2.2:
|
||||||
|
- Changed not to work when 'paste' option is set.
|
||||||
|
- Fixed AutoComplPopEnable command and AutoComplPopDisable command to
|
||||||
|
map/unmap "i" and "R".
|
||||||
|
|
||||||
|
2.1:
|
||||||
|
- Fixed the problem caused by "." command in Normal mode.
|
||||||
|
- Changed to map "i" and "R" to feed completion command after starting
|
||||||
|
Insert mode.
|
||||||
|
- Avoided the problem caused by Windows IME.
|
||||||
|
|
||||||
|
2.0:
|
||||||
|
- Changed to use CursorMovedI event to feed a completion command instead of
|
||||||
|
key mapping. Now the auto-popup is triggered by moving the cursor.
|
||||||
|
- Changed to feed completion command after starting Insert mode.
|
||||||
|
- Removed g:AutoComplPop_MapList option.
|
||||||
|
|
||||||
|
1.7:
|
||||||
|
- Added behaviors for HTML/XHTML. Now supports the omni completion for
|
||||||
|
HTML/XHTML.
|
||||||
|
- Changed not to show expressions for CTRL-R =.
|
||||||
|
- Changed not to set 'nolazyredraw' while a popup menu is visible.
|
||||||
|
|
||||||
|
1.6.1:
|
||||||
|
- Changed not to trigger the filename completion by a text which has
|
||||||
|
multi-byte characters.
|
||||||
|
|
||||||
|
1.6:
|
||||||
|
- Redesigned g:AutoComplPop_Behavior option.
|
||||||
|
- Changed default value of g:AutoComplPop_CompleteOption option.
|
||||||
|
- Changed default value of g:AutoComplPop_MapList option.
|
||||||
|
|
||||||
|
1.5:
|
||||||
|
- Implemented continuous-completion for the filename completion. And added
|
||||||
|
new option to g:AutoComplPop_Behavior.
|
||||||
|
|
||||||
|
1.4:
|
||||||
|
- Fixed the bug that the auto-popup was not suspended in fuzzyfinder.
|
||||||
|
- Fixed the bug that an error has occurred with Ruby-omni-completion unless
|
||||||
|
Ruby interface.
|
||||||
|
|
||||||
|
1.3:
|
||||||
|
- Supported Ruby-omni-completion by default.
|
||||||
|
- Supported filename completion by default.
|
||||||
|
- Added g:AutoComplPop_Behavior option.
|
||||||
|
- Added g:AutoComplPop_CompleteoptPreview option.
|
||||||
|
- Removed g:AutoComplPop_MinLength option.
|
||||||
|
- Removed g:AutoComplPop_MaxLength option.
|
||||||
|
- Removed g:AutoComplPop_PopupCmd option.
|
||||||
|
|
||||||
|
1.2:
|
||||||
|
- Fixed bugs related to 'completeopt'.
|
||||||
|
|
||||||
|
1.1:
|
||||||
|
- Added g:AutoComplPop_IgnoreCaseOption option.
|
||||||
|
- Added g:AutoComplPop_NotEnableAtStartup option.
|
||||||
|
- Removed g:AutoComplPop_LoadAndEnable option.
|
||||||
|
1.0:
|
||||||
|
- g:AutoComplPop_LoadAndEnable option for a startup activation is added.
|
||||||
|
- AutoComplPopLock command and AutoComplPopUnlock command are added to
|
||||||
|
suspend and resume.
|
||||||
|
- 'completeopt' and 'complete' options are changed temporarily while
|
||||||
|
completing by this script.
|
||||||
|
|
||||||
|
0.4:
|
||||||
|
- The first match are selected when the popup menu is Opened. You can insert
|
||||||
|
the first match with CTRL-Y.
|
||||||
|
|
||||||
|
0.3:
|
||||||
|
- Fixed the problem that the original text is not restored if 'longest' is
|
||||||
|
not set in 'completeopt'. Now the plugin works whether or not 'longest' is
|
||||||
|
set in 'completeopt', and also 'menuone'.
|
||||||
|
|
||||||
|
0.2:
|
||||||
|
- When completion matches are not found, insert CTRL-E to stop completion.
|
||||||
|
- Clear the echo area.
|
||||||
|
- Fixed the problem in case of dividing words by symbols, popup menu is
|
||||||
|
not opened.
|
||||||
|
|
||||||
|
0.1:
|
||||||
|
- First release.
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
ABOUT *acp-about* *acp-contact* *acp-author*
|
||||||
|
|
||||||
|
Author: Takeshi NISHIDA <ns9tks@DELETE-ME.gmail.com>
|
||||||
|
Licence: MIT Licence
|
||||||
|
URL: http://www.vim.org/scripts/script.php?script_id=1879
|
||||||
|
http://bitbucket.org/ns9tks/vim-autocomplpop/
|
||||||
|
|
||||||
|
Bugs/Issues/Suggestions/Improvements ~
|
||||||
|
|
||||||
|
Please submit to http://bitbucket.org/ns9tks/vim-autocomplpop/issues/ .
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
vim:tw=78:ts=8:ft=help:norl:
|
||||||
|
|
||||||
@@ -85,16 +85,15 @@ function! ShowPyDoc(name, type)
|
|||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
"highlighting
|
||||||
function! Highlight(name)
|
function! Highlight(name)
|
||||||
execute "sb __doc__"
|
execute "sb __doc__"
|
||||||
set filetype=man
|
set filetype=man
|
||||||
syn on
|
"syn on
|
||||||
execute 'syntax keyword pydoc '.s:name2
|
execute 'syntax keyword pydoc '.a:name
|
||||||
hi pydoc gui=reverse
|
hi pydoc gui=reverse
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
|
||||||
"mappings
|
"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-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>pW :call ShowPyDoc('<C-R><C-A>', 1)<CR>
|
||||||
|
|||||||
@@ -46,14 +46,20 @@ if sys.version_info[:2] < (2, 5):
|
|||||||
scriptdir = os.path.join(os.path.dirname(vim.eval('expand("<sfile>")')), 'pyflakes')
|
scriptdir = os.path.join(os.path.dirname(vim.eval('expand("<sfile>")')), 'pyflakes')
|
||||||
sys.path.insert(0, scriptdir)
|
sys.path.insert(0, scriptdir)
|
||||||
|
|
||||||
from pyflakes import checker, ast, messages
|
import ast
|
||||||
|
from pyflakes import checker, messages
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
class loc(object):
|
||||||
|
def __init__(self, lineno, col=None):
|
||||||
|
self.lineno = lineno
|
||||||
|
self.col_offset = col
|
||||||
|
|
||||||
class SyntaxError(messages.Message):
|
class SyntaxError(messages.Message):
|
||||||
message = 'could not compile: %s'
|
message = 'could not compile: %s'
|
||||||
def __init__(self, filename, lineno, col, message):
|
def __init__(self, filename, lineno, col, message):
|
||||||
messages.Message.__init__(self, filename, lineno, col)
|
messages.Message.__init__(self, filename, loc(lineno, col))
|
||||||
self.message_args = (message,)
|
self.message_args = (message,)
|
||||||
|
|
||||||
class blackhole(object):
|
class blackhole(object):
|
||||||
@@ -67,13 +73,12 @@ def check(buffer):
|
|||||||
# assume everything else that follows is encoded in the encoding.
|
# assume everything else that follows is encoded in the encoding.
|
||||||
encoding_found = False
|
encoding_found = False
|
||||||
for n, line in enumerate(contents):
|
for n, line in enumerate(contents):
|
||||||
if not encoding_found:
|
if n >= 2:
|
||||||
if re.match(r'^# -\*- coding: .+? -*-', line):
|
|
||||||
encoding_found = True
|
|
||||||
else:
|
|
||||||
# skip all preceeding lines
|
|
||||||
contents = [''] * n + contents[n:]
|
|
||||||
break
|
break
|
||||||
|
elif re.match(r'#.*coding[:=]\s*([-\w.]+)', line):
|
||||||
|
contents = ['']*(n+1) + contents[n+1:]
|
||||||
|
break
|
||||||
|
|
||||||
contents = '\n'.join(contents) + '\n'
|
contents = '\n'.join(contents) + '\n'
|
||||||
|
|
||||||
vimenc = vim.eval('&encoding')
|
vimenc = vim.eval('&encoding')
|
||||||
@@ -82,7 +87,7 @@ def check(buffer):
|
|||||||
|
|
||||||
builtins = []
|
builtins = []
|
||||||
try:
|
try:
|
||||||
builtins = eval(vim.eval('string(g:pyflakes_builtins)'))
|
builtins = set(eval(vim.eval('string(g:pyflakes_builtins)')))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -90,7 +95,7 @@ def check(buffer):
|
|||||||
# TODO: use warnings filters instead of ignoring stderr
|
# TODO: use warnings filters instead of ignoring stderr
|
||||||
old_stderr, sys.stderr = sys.stderr, blackhole()
|
old_stderr, sys.stderr = sys.stderr, blackhole()
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(contents, filename)
|
tree = ast.parse(contents, filename or '<unknown>')
|
||||||
finally:
|
finally:
|
||||||
sys.stderr = old_stderr
|
sys.stderr = old_stderr
|
||||||
except:
|
except:
|
||||||
@@ -104,7 +109,15 @@ def check(buffer):
|
|||||||
|
|
||||||
return [SyntaxError(filename, lineno, offset, str(value))]
|
return [SyntaxError(filename, lineno, offset, str(value))]
|
||||||
else:
|
else:
|
||||||
w = checker.Checker(tree, filename, builtins = builtins)
|
# pyflakes looks to _MAGIC_GLOBALS in checker.py to see which
|
||||||
|
# UndefinedNames to ignore
|
||||||
|
old_globals = getattr(checker,' _MAGIC_GLOBALS', [])
|
||||||
|
checker._MAGIC_GLOBALS = set(old_globals) | builtins
|
||||||
|
|
||||||
|
w = checker.Checker(tree, filename)
|
||||||
|
|
||||||
|
checker._MAGIC_GLOBALS = old_globals
|
||||||
|
|
||||||
w.messages.sort(key = attrgetter('lineno'))
|
w.messages.sort(key = attrgetter('lineno'))
|
||||||
return w.messages
|
return w.messages
|
||||||
|
|
||||||
@@ -233,7 +246,7 @@ for w in check(vim.current.buffer):
|
|||||||
vim.command("let l:qf_item.text = '%s'" % vim_quote(w.message % w.message_args))
|
vim.command("let l:qf_item.text = '%s'" % vim_quote(w.message % w.message_args))
|
||||||
vim.command("let l:qf_item.type = 'E'")
|
vim.command("let l:qf_item.type = 'E'")
|
||||||
|
|
||||||
if w.col is None or isinstance(w, SyntaxError):
|
if getattr(w, 'col', None) is None or isinstance(w, SyntaxError):
|
||||||
# without column information, just highlight the whole line
|
# without column information, just highlight the whole line
|
||||||
# (minus the newline)
|
# (minus the newline)
|
||||||
vim.command(r"let s:mID = matchadd('PyFlakes', '\%" + str(w.lineno) + r"l\n\@!')")
|
vim.command(r"let s:mID = matchadd('PyFlakes', '\%" + str(w.lineno) + r"l\n\@!')")
|
||||||
|
|||||||
29
ftplugin/python/pyflakes/NEWS.txt
Normal file
29
ftplugin/python/pyflakes/NEWS.txt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
0.4.0 (2009-11-25):
|
||||||
|
- Fix reporting for certain SyntaxErrors which lack line number
|
||||||
|
information.
|
||||||
|
- Check for syntax errors more rigorously.
|
||||||
|
- Support checking names used with the class decorator syntax in versions
|
||||||
|
of Python which have it.
|
||||||
|
- Detect local variables which are bound but never used.
|
||||||
|
- Handle permission errors when trying to read source files.
|
||||||
|
- Handle problems with the encoding of source files.
|
||||||
|
- Support importing dotted names so as not to incorrectly report them as
|
||||||
|
redefined unused names.
|
||||||
|
- Support all forms of the with statement.
|
||||||
|
- Consider static `__all__` definitions and avoid reporting unused names
|
||||||
|
if the names are listed there.
|
||||||
|
- Fix incorrect checking of class names with respect to the names of their
|
||||||
|
bases in the class statement.
|
||||||
|
- Support the `__path__` global in `__init__.py`.
|
||||||
|
|
||||||
|
0.3.0 (2009-01-30):
|
||||||
|
- Display more informative SyntaxError messages.
|
||||||
|
- Don't hang flymake with unmatched triple quotes (only report a single
|
||||||
|
line of source for a multiline syntax error).
|
||||||
|
- Recognize __builtins__ as a defined name.
|
||||||
|
- Improve pyflakes support for python versions 2.3-2.5
|
||||||
|
- Support for if-else expressions and with statements.
|
||||||
|
- Warn instead of error on non-existant file paths.
|
||||||
|
- Check for __future__ imports after other statements.
|
||||||
|
- Add reporting for some types of import shadowing.
|
||||||
|
- Improve reporting of unbound locals
|
||||||
@@ -1,36 +1,80 @@
|
|||||||
pyflakes
|
pyflakes-vim
|
||||||
========
|
============
|
||||||
|
|
||||||
This version of PyFlakes_ has been improved to use Python's newer ``ast``
|
A Vim plugin for checking Python code on the fly.
|
||||||
module, instead of ``compiler``. So code checking happens faster, and will stay
|
|
||||||
up to date with new language changes.
|
|
||||||
|
|
||||||
.. _PyFlakes: http://www.divmod.org/trac/wiki/DivmodPyflakes
|
PyFlakes catches common Python errors like mistyping a variable name or
|
||||||
|
accessing a local before it is bound, and also gives warnings for things like
|
||||||
|
unused imports.
|
||||||
|
|
||||||
|
pyflakes-vim uses the output from PyFlakes to highlight errors in your code.
|
||||||
|
To locate errors quickly, use quickfix_ commands like :cc.
|
||||||
|
|
||||||
|
Make sure to check vim.org_ for the latest updates.
|
||||||
|
|
||||||
|
.. _pyflakes.vim: http://www.vim.org/scripts/script.php?script_id=2441
|
||||||
|
.. _vim.org: http://www.vim.org/scripts/script.php?script_id=2441
|
||||||
|
.. _quickfix: http://vimdoc.sourceforge.net/htmldoc/quickfix.html#quickfix
|
||||||
|
|
||||||
|
Quick Installation
|
||||||
|
------------------
|
||||||
|
|
||||||
|
1. Make sure your ``.vimrc`` has::
|
||||||
|
|
||||||
|
filetype on " enables filetype detection
|
||||||
|
filetype plugin on " enables filetype specific plugins
|
||||||
|
|
||||||
|
2. Download the latest release_.
|
||||||
|
|
||||||
|
3. Unzip ``pyflakes.vim`` and the ``pyflakes`` directory into
|
||||||
|
``~/.vim/ftplugin/python`` (or somewhere similar on your
|
||||||
|
`runtime path`_ that will be sourced for Python files).
|
||||||
|
|
||||||
|
.. _release: http://www.vim.org/scripts/script.php?script_id=2441
|
||||||
|
.. _runtime path: http://vimdoc.sourceforge.net/htmldoc/options.html#'runtimepath'
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you downloaded this from vim.org_, then just drop the contents of the zip
|
||||||
|
file into ``~/.vim/ftplugin/python``.
|
||||||
|
|
||||||
|
Otherwise, if you're running "from source," you'll need PyFlakes on your
|
||||||
|
PYTHONPATH somewhere. I recommend getting my PyFlakes_ fork, which retains
|
||||||
|
column number information and has therfore has more specific error locations.
|
||||||
|
|
||||||
|
.. _vim.org: http://www.vim.org/scripts/script.php?script_id=2441
|
||||||
|
.. _PyFlakes: http://github.com/kevinw/pyflakes
|
||||||
|
|
||||||
|
Hacking
|
||||||
|
-------
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
git clone git://github.com/kevinw/pyflakes-vim.git
|
||||||
|
cd pyflakes-vim
|
||||||
|
git clone git://github.com/kevinw/pyflakes.git
|
||||||
|
|
||||||
|
Options
|
||||||
|
-------
|
||||||
|
|
||||||
|
Set this option to you vimrc file to disable quickfix support::
|
||||||
|
|
||||||
|
let g:pyflakes_use_quickfix = 0
|
||||||
|
|
||||||
|
The value is set to 1 by default.
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
----
|
----
|
||||||
|
* signs_ support (show warning and error icons to left of the buffer area)
|
||||||
|
* configuration variables
|
||||||
|
* parse or intercept useful output from the warnings module
|
||||||
|
|
||||||
Importing several modules from the same package results in unnecessary warnings:
|
.. _signs: http://www.vim.org/htmldoc/sign.html
|
||||||
|
|
||||||
::
|
Changelog
|
||||||
|
---------
|
||||||
|
|
||||||
import a.b
|
Please see http://www.vim.org/scripts/script.php?script_id=2441 for a history of
|
||||||
import a.c # Redefinition of unused "a" from line 1
|
all changes.
|
||||||
|
|
||||||
The following construct for defining a function differently depending on some
|
|
||||||
condition results in a redefinition warning:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
if some_condition:
|
|
||||||
def foo(): do_foo()
|
|
||||||
else:
|
|
||||||
def foo(): do_bar() # redefinition of function 'foo' from line 2
|
|
||||||
|
|
||||||
IDE Integration
|
|
||||||
---------------
|
|
||||||
|
|
||||||
* vim: pyflakes-vim_
|
|
||||||
|
|
||||||
.. _pyflakes-vim: http://github.com/kevinw/pyflakes-vim
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
- Check for methods that override other methods except that they vary by case.
|
|
||||||
- assign/increment + unbound local error not caught
|
|
||||||
def foo():
|
|
||||||
bar = 5
|
|
||||||
def meep():
|
|
||||||
bar += 2
|
|
||||||
meep()
|
|
||||||
print bar
|
|
||||||
|
|
||||||
print foo()
|
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
__version__ = '0.4.0'
|
||||||
|
|||||||
@@ -1,54 +1,147 @@
|
|||||||
import ast
|
# -*- test-case-name: pyflakes -*-
|
||||||
from pyflakes import messages
|
# (c) 2005-2010 Divmod, Inc.
|
||||||
|
# See LICENSE file for details
|
||||||
|
|
||||||
import __builtin__
|
import __builtin__
|
||||||
|
import os.path
|
||||||
|
import _ast
|
||||||
|
|
||||||
|
from pyflakes import messages
|
||||||
|
|
||||||
|
|
||||||
allowed_before_future = (ast.Module, ast.ImportFrom, ast.Expr, ast.Str)
|
# utility function to iterate over an AST node's children, adapted
|
||||||
defined_names = set(('__file__', '__builtins__'))
|
# 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):
|
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
|
@ivar used: pair of (L{Scope}, line-number) indicating the scope and
|
||||||
line number that this binding was last used
|
line number that this binding was last used
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, source):
|
def __init__(self, name, source):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.source = source
|
self.source = source
|
||||||
self.used = False
|
self.used = False
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
|
return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
|
||||||
self.name,
|
self.name,
|
||||||
self.source.lineno,
|
self.source.lineno,
|
||||||
id(self))
|
id(self))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class UnBinding(Binding):
|
class UnBinding(Binding):
|
||||||
'''Created by the 'del' operator.'''
|
'''Created by the 'del' operator.'''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Importation(Binding):
|
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):
|
def __init__(self, name, source):
|
||||||
|
self.fullName = name
|
||||||
name = name.split('.')[0]
|
name = name.split('.')[0]
|
||||||
super(Importation, self).__init__(name, source)
|
super(Importation, self).__init__(name, source)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Argument(Binding):
|
||||||
|
"""
|
||||||
|
Represents binding a name as an argument.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Assignment(Binding):
|
class Assignment(Binding):
|
||||||
pass
|
"""
|
||||||
|
Represents binding a name with an explicit assignment.
|
||||||
|
|
||||||
|
The checker will raise warnings for any Assignment that isn't used. Also,
|
||||||
|
the checker does not consider assignments in tuple/list unpacking to be
|
||||||
|
Assignments, rather it treats them as simple Bindings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionDefinition(Binding):
|
class FunctionDefinition(Binding):
|
||||||
_property_decorator = False
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ExportBinding(Binding):
|
||||||
|
"""
|
||||||
|
A binding created by an C{__all__} assignment. If the names in the list
|
||||||
|
can be determined statically, they will be treated as names for export and
|
||||||
|
additional checking applied to them.
|
||||||
|
|
||||||
|
The only C{__all__} assignment that can be recognized is one which takes
|
||||||
|
the value of a literal list containing literal strings. For example::
|
||||||
|
|
||||||
|
__all__ = ["foo", "bar"]
|
||||||
|
|
||||||
|
Names which are imported and not otherwise used but appear in the value of
|
||||||
|
C{__all__} will not have an unused import warning reported for them.
|
||||||
|
"""
|
||||||
|
def names(self):
|
||||||
|
"""
|
||||||
|
Return a list of the names referenced by this binding.
|
||||||
|
"""
|
||||||
|
names = []
|
||||||
|
if isinstance(self.source, _ast.List):
|
||||||
|
for node in self.source.elts:
|
||||||
|
if isinstance(node, _ast.Str):
|
||||||
|
names.append(node.s)
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Scope(dict):
|
class Scope(dict):
|
||||||
import_starred = False # set to True when import * is found
|
importStarred = False # set to True when import * is found
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
|
return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Scope, self).__init__()
|
super(Scope, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ClassScope(Scope):
|
class ClassScope(Scope):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -69,340 +162,464 @@ class FunctionScope(Scope):
|
|||||||
class ModuleScope(Scope):
|
class ModuleScope(Scope):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Checker(ast.NodeVisitor):
|
|
||||||
def __init__(self, tree, filename='(none)', builtins = None):
|
|
||||||
ast.NodeVisitor.__init__(self)
|
|
||||||
|
|
||||||
self.deferred = []
|
# Globally defined names which are not attributes of the __builtin__ module.
|
||||||
|
_MAGIC_GLOBALS = ['__file__', '__builtins__']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Checker(object):
|
||||||
|
"""
|
||||||
|
I check the cleanliness and sanity of Python code.
|
||||||
|
|
||||||
|
@ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements
|
||||||
|
of the list are two-tuples. The first element is the callable passed
|
||||||
|
to L{deferFunction}. The second element is a copy of the scope stack
|
||||||
|
at the time L{deferFunction} was called.
|
||||||
|
|
||||||
|
@ivar _deferredAssignments: Similar to C{_deferredFunctions}, but for
|
||||||
|
callables which are deferred assignment checks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
nodeDepth = 0
|
||||||
|
traceTree = False
|
||||||
|
|
||||||
|
def __init__(self, tree, filename='(none)'):
|
||||||
|
self._deferredFunctions = []
|
||||||
|
self._deferredAssignments = []
|
||||||
self.dead_scopes = []
|
self.dead_scopes = []
|
||||||
self.messages = []
|
self.messages = []
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.scope_stack = [ModuleScope()]
|
self.scopeStack = [ModuleScope()]
|
||||||
self.futures_allowed = True
|
self.futuresAllowed = True
|
||||||
self.builtins = frozenset(builtins or [])
|
self.handleChildren(tree)
|
||||||
|
self._runDeferred(self._deferredFunctions)
|
||||||
self.visit(tree)
|
# Set _deferredFunctions to None so that deferFunction will fail
|
||||||
for handler, scope in self.deferred:
|
# noisily if called after we've run through the deferred functions.
|
||||||
self.scope_stack = scope
|
self._deferredFunctions = None
|
||||||
handler()
|
self._runDeferred(self._deferredAssignments)
|
||||||
del self.scope_stack[1:]
|
# Set _deferredAssignments to None so that deferAssignment will fail
|
||||||
self.pop_scope()
|
# noisly if called after we've run through the deferred assignments.
|
||||||
|
self._deferredAssignments = None
|
||||||
|
del self.scopeStack[1:]
|
||||||
|
self.popScope()
|
||||||
self.check_dead_scopes()
|
self.check_dead_scopes()
|
||||||
|
|
||||||
def defer(self, callable):
|
|
||||||
'''Schedule something to be called after just before completion.
|
def deferFunction(self, callable):
|
||||||
|
'''
|
||||||
|
Schedule a function handler to be called just before completion.
|
||||||
|
|
||||||
This is used for handling function bodies, which must be deferred
|
This is used for handling function bodies, which must be deferred
|
||||||
because code later in the file might modify the global scope. When
|
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
|
`callable` is called, the scope at the time this is called will be
|
||||||
restored, however it will contain any new bindings added to it.
|
restored, however it will contain any new bindings added to it.
|
||||||
'''
|
'''
|
||||||
self.deferred.append( (callable, self.scope_stack[:]) )
|
self._deferredFunctions.append((callable, self.scopeStack[:]))
|
||||||
|
|
||||||
|
|
||||||
|
def deferAssignment(self, callable):
|
||||||
|
"""
|
||||||
|
Schedule an assignment handler to be called just after deferred
|
||||||
|
function handlers.
|
||||||
|
"""
|
||||||
|
self._deferredAssignments.append((callable, self.scopeStack[:]))
|
||||||
|
|
||||||
|
|
||||||
|
def _runDeferred(self, deferred):
|
||||||
|
"""
|
||||||
|
Run the callables in C{deferred} using their associated scope stack.
|
||||||
|
"""
|
||||||
|
for handler, scope in deferred:
|
||||||
|
self.scopeStack = scope
|
||||||
|
handler()
|
||||||
|
|
||||||
|
|
||||||
|
def scope(self):
|
||||||
|
return self.scopeStack[-1]
|
||||||
|
scope = property(scope)
|
||||||
|
|
||||||
|
def popScope(self):
|
||||||
|
self.dead_scopes.append(self.scopeStack.pop())
|
||||||
|
|
||||||
|
|
||||||
def check_dead_scopes(self):
|
def check_dead_scopes(self):
|
||||||
# Check for modules that were imported but unused
|
"""
|
||||||
|
Look at scopes which have been fully examined and report names in them
|
||||||
|
which were imported but unused.
|
||||||
|
"""
|
||||||
for scope in self.dead_scopes:
|
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():
|
for importation in scope.itervalues():
|
||||||
if isinstance(importation, Importation) and not importation.used:
|
if isinstance(importation, Importation):
|
||||||
self.report(messages.UnusedImport, importation.source.lineno, importation.name)
|
if not importation.used and importation.name not in all:
|
||||||
|
self.report(
|
||||||
|
messages.UnusedImport,
|
||||||
|
importation.source,
|
||||||
|
importation.name)
|
||||||
|
|
||||||
def push_function_scope(self):
|
|
||||||
self.scope_stack.append(FunctionScope())
|
|
||||||
|
|
||||||
def push_class_scope(self):
|
def pushFunctionScope(self):
|
||||||
self.scope_stack.append(ClassScope())
|
self.scopeStack.append(FunctionScope())
|
||||||
|
|
||||||
def pop_scope(self):
|
def pushClassScope(self):
|
||||||
scope = self.scope_stack.pop()
|
self.scopeStack.append(ClassScope())
|
||||||
self.dead_scopes.append(scope)
|
|
||||||
|
|
||||||
@property
|
def report(self, messageClass, *args, **kwargs):
|
||||||
def scope(self):
|
self.messages.append(messageClass(self.filename, *args, **kwargs))
|
||||||
return self.scope_stack[-1]
|
|
||||||
|
|
||||||
def report(self, message_class, *args, **kwargs):
|
def handleChildren(self, tree):
|
||||||
self.messages.append(message_class(self.filename, *args, **kwargs))
|
for node in iter_child_nodes(tree):
|
||||||
|
self.handleNode(node, tree)
|
||||||
|
|
||||||
def visit_Import(self, node):
|
def isDocstring(self, node):
|
||||||
for name_node in node.names:
|
"""
|
||||||
# "import bar as foo" -> name=bar, asname=foo
|
Determine if the given node is a docstring, as long as it is at the
|
||||||
name = name_node.asname or name_node.name
|
correct place in the node tree.
|
||||||
self.add_binding(node, Importation(name, node))
|
"""
|
||||||
|
return isinstance(node, _ast.Str) or \
|
||||||
|
(isinstance(node, _ast.Expr) and
|
||||||
|
isinstance(node.value, _ast.Str))
|
||||||
|
|
||||||
def visit_GeneratorExp(self, node):
|
def handleNode(self, node, parent):
|
||||||
for generator in node.generators:
|
node.parent = parent
|
||||||
self.visit(generator.iter)
|
if self.traceTree:
|
||||||
self.assign_vars(generator.target)
|
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__
|
||||||
|
|
||||||
for generator in node.generators:
|
def ignore(self, node):
|
||||||
if hasattr(node, 'elt'):
|
pass
|
||||||
self.visit(node.elt)
|
|
||||||
|
|
||||||
self.visit_nodes(generator.ifs)
|
# "stmt" type nodes
|
||||||
|
RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \
|
||||||
|
TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren
|
||||||
|
|
||||||
visit_ListComp = visit_GeneratorExp
|
CONTINUE = BREAK = PASS = ignore
|
||||||
|
|
||||||
def visit_For(self, node):
|
# "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.
|
Process bindings for loop variables.
|
||||||
'''
|
"""
|
||||||
self.visit_nodes(node.iter)
|
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)
|
||||||
|
|
||||||
for var in self.flatten(node.target):
|
collectLoopVars(node.target)
|
||||||
upval = self.scope.get(var.id)
|
for varn in vars:
|
||||||
if isinstance(upval, Importation) and upval.used:
|
if (isinstance(self.scope.get(varn), Importation)
|
||||||
|
# unused ones will get an unused import warning
|
||||||
|
and self.scope[varn].used):
|
||||||
self.report(messages.ImportShadowedByLoopVar,
|
self.report(messages.ImportShadowedByLoopVar,
|
||||||
node.lineno, node.col_offset, var.id, upval.source.lineno)
|
node, varn, self.scope[varn].source)
|
||||||
|
|
||||||
self.add_binding(var, Assignment(var.id, var))
|
self.handleChildren(node)
|
||||||
|
|
||||||
self.visit_nodes(node.body + node.orelse)
|
|
||||||
|
|
||||||
def visit_FunctionDef(self, node):
|
|
||||||
|
|
||||||
try:
|
|
||||||
decorators = node.decorator_list
|
|
||||||
except AttributeError:
|
|
||||||
# Use .decorators for Python 2.5 compatibility
|
|
||||||
decorators = node.decorators
|
|
||||||
|
|
||||||
self.visit_nodes(decorators)
|
|
||||||
|
|
||||||
# Check for property decorator
|
|
||||||
func_def = FunctionDefinition(node.name, node)
|
|
||||||
|
|
||||||
for decorator in decorators:
|
|
||||||
if getattr(decorator, 'attr', None) in ('setter', 'deleter'):
|
|
||||||
func_def._property_decorator = True
|
|
||||||
|
|
||||||
self.add_binding(node, func_def)
|
|
||||||
|
|
||||||
self.visit_Lambda(node)
|
|
||||||
|
|
||||||
def visit_Lambda(self, node):
|
|
||||||
self.visit_nodes(node.args.defaults)
|
|
||||||
|
|
||||||
def run_function():
|
|
||||||
self.push_function_scope()
|
|
||||||
|
|
||||||
# Check for duplicate arguments
|
|
||||||
argnames = set()
|
|
||||||
for arg in self.flatten(node.args.args):
|
|
||||||
if arg.id in argnames:
|
|
||||||
self.report(messages.DuplicateArgument, arg.lineno, arg.col_offset, arg.id)
|
|
||||||
argnames.add(arg.id)
|
|
||||||
|
|
||||||
self.assign_vars(node.args.args, report_redef=False)
|
|
||||||
if node.args.vararg is not None:
|
|
||||||
self.add_binding(node, Assignment(node.args.vararg, node), False)
|
|
||||||
if node.args.kwarg is not None:
|
|
||||||
self.add_binding(node, Assignment(node.args.kwarg, node), False)
|
|
||||||
self.visit_nodes(node.body)
|
|
||||||
self.pop_scope()
|
|
||||||
|
|
||||||
self.defer(run_function)
|
|
||||||
|
|
||||||
def visit_Name(self, node):
|
|
||||||
'''
|
|
||||||
Locate names in locals / function / globals scopes.
|
|
||||||
'''
|
|
||||||
scope, name = self.scope, node.id
|
|
||||||
|
|
||||||
|
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
|
# try local scope
|
||||||
import_starred = scope.import_starred
|
importStarred = self.scope.importStarred
|
||||||
try:
|
try:
|
||||||
scope[name].used = (scope, node.lineno, node.col_offset)
|
self.scope[node.id].used = (self.scope, node)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
# try enclosing function scopes
|
# try enclosing function scopes
|
||||||
for func_scope in self.scope_stack[-2:0:-1]:
|
|
||||||
import_starred = import_starred or func_scope.import_starred
|
for scope in self.scopeStack[-2:0:-1]:
|
||||||
if not isinstance(func_scope, FunctionScope):
|
importStarred = importStarred or scope.importStarred
|
||||||
|
if not isinstance(scope, FunctionScope):
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
func_scope[name].used = (scope, node.lineno, node.col_offset)
|
scope[node.id].used = (self.scope, node)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
# try global scope
|
# try global scope
|
||||||
import_starred = import_starred or self.scope_stack[0].import_starred
|
|
||||||
|
importStarred = importStarred or self.scopeStack[0].importStarred
|
||||||
try:
|
try:
|
||||||
self.scope_stack[0][node.id].used = (scope, node.lineno, node.col_offset)
|
self.scopeStack[0][node.id].used = (self.scope, node)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if not import_starred and not self.is_builtin(name):
|
if ((not hasattr(__builtin__, node.id))
|
||||||
self.report(messages.UndefinedName, node.lineno, node.col_offset, name)
|
and node.id not in _MAGIC_GLOBALS
|
||||||
|
and not importStarred):
|
||||||
def assign_vars(self, targets, report_redef=True):
|
if (os.path.basename(self.filename) == '__init__.py' and
|
||||||
scope = self.scope
|
node.id == '__path__'):
|
||||||
|
# the special name __path__ is valid only in packages
|
||||||
for target in self.flatten(targets):
|
pass
|
||||||
name = target.id
|
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 the name hasn't already been defined in the current scope
|
||||||
if isinstance(scope, FunctionScope) and name not in scope:
|
if isinstance(self.scope, FunctionScope) and node.id not in self.scope:
|
||||||
# for each function or module scope above us
|
# for each function or module scope above us
|
||||||
for upscope in self.scope_stack[:-1]:
|
for scope in self.scopeStack[:-1]:
|
||||||
if not isinstance(upscope, (FunctionScope, ModuleScope)):
|
if not isinstance(scope, (FunctionScope, ModuleScope)):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
upval = upscope.get(name)
|
|
||||||
# if the name was defined in that scope, and the name has
|
# if the name was defined in that scope, and the name has
|
||||||
# been accessed already in the current scope, and hasn't
|
# been accessed already in the current scope, and hasn't
|
||||||
# been declared global
|
# been declared global
|
||||||
if upval is not None:
|
if (node.id in scope
|
||||||
if upval.used and upval.used[0] is scope and name not in scope.globals:
|
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
|
# then it's probably a mistake
|
||||||
self.report(messages.UndefinedLocal,
|
self.report(messages.UndefinedLocal,
|
||||||
upval.used[1], upval.used[2], name, upval.source.lineno, upval.source.col_offset)
|
scope[node.id].used[1],
|
||||||
|
node.id,
|
||||||
|
scope[node.id].source)
|
||||||
|
break
|
||||||
|
|
||||||
self.add_binding(target, Assignment(name, target), report_redef)
|
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 visit_Assign(self, node):
|
|
||||||
|
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:
|
for target in node.targets:
|
||||||
self.visit_nodes(node.value)
|
self.handleNode(target, node)
|
||||||
self.assign_vars(node.targets)
|
|
||||||
|
|
||||||
def visit_Delete(self, node):
|
def AUGASSIGN(self, node):
|
||||||
for target in self.flatten(node.targets):
|
# AugAssign is awkward: must set the context explicitly and visit twice,
|
||||||
if isinstance(self.scope, FunctionScope) and target.id in self.scope.globals:
|
# once with AugLoad context, once with AugStore context
|
||||||
del self.scope.globals[target.id]
|
node.target.ctx = _ast.AugLoad()
|
||||||
else:
|
self.handleNode(node.target, node)
|
||||||
self.add_binding(target, UnBinding(target.id, target))
|
self.handleNode(node.value, node)
|
||||||
|
node.target.ctx = _ast.AugStore()
|
||||||
|
self.handleNode(node.target, node)
|
||||||
|
|
||||||
def visit_With(self, node):
|
def IMPORT(self, node):
|
||||||
self.visit(node.context_expr)
|
for alias in node.names:
|
||||||
|
name = alias.asname or alias.name
|
||||||
|
importation = Importation(name, node)
|
||||||
|
self.addBinding(node, importation)
|
||||||
|
|
||||||
# handle new bindings made by optional "as" part
|
def IMPORTFROM(self, node):
|
||||||
if node.optional_vars is not None:
|
|
||||||
self.assign_vars(node.optional_vars)
|
|
||||||
|
|
||||||
self.visit_nodes(node.body)
|
|
||||||
|
|
||||||
def visit_ImportFrom(self, node):
|
|
||||||
if node.module == '__future__':
|
if node.module == '__future__':
|
||||||
if not self.futures_allowed:
|
if not self.futuresAllowed:
|
||||||
self.report(messages.LateFutureImport, node.lineno, node.col_offset, [alias.name for alias in node.names])
|
self.report(messages.LateFutureImport, node,
|
||||||
|
[n.name for n in node.names])
|
||||||
else:
|
else:
|
||||||
self.futures_allowed = False
|
self.futuresAllowed = False
|
||||||
|
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
if alias.name == '*':
|
if alias.name == '*':
|
||||||
self.scope.import_starred = True
|
self.scope.importStarred = True
|
||||||
self.report(messages.ImportStarUsed, node.lineno, node.col_offset, node.module)
|
self.report(messages.ImportStarUsed, node, node.module)
|
||||||
continue
|
continue
|
||||||
name = alias.asname or alias.name
|
name = alias.asname or alias.name
|
||||||
importation = Importation(name, node)
|
importation = Importation(name, node)
|
||||||
if node.module == '__future__':
|
if node.module == '__future__':
|
||||||
importation.used = (self.scope, node.lineno, node.col_offset)
|
importation.used = (self.scope, node)
|
||||||
self.add_binding(node, importation)
|
self.addBinding(node, importation)
|
||||||
|
|
||||||
def visit_Global(self, node):
|
|
||||||
'''
|
|
||||||
Keep track of global declarations.
|
|
||||||
'''
|
|
||||||
scope = self.scope
|
|
||||||
if isinstance(scope, FunctionScope):
|
|
||||||
scope.globals.update(dict.fromkeys(node.names))
|
|
||||||
|
|
||||||
def visit_ClassDef(self, node):
|
|
||||||
try:
|
|
||||||
decorators = node.decorator_list
|
|
||||||
except AttributeError:
|
|
||||||
# Use .decorators for Python 2.5 compatibility
|
|
||||||
decorators = getattr(node, 'decorators', [])
|
|
||||||
|
|
||||||
self.visit_nodes(decorators)
|
|
||||||
|
|
||||||
self.add_binding(node, Assignment(node.name, node))
|
|
||||||
self.visit_nodes(node.bases)
|
|
||||||
|
|
||||||
self.push_class_scope()
|
|
||||||
self.visit_nodes(node.body)
|
|
||||||
self.pop_scope()
|
|
||||||
|
|
||||||
def visit_excepthandler(self, node):
|
|
||||||
if node.type is not None:
|
|
||||||
self.visit(node.type)
|
|
||||||
if node.name is not None:
|
|
||||||
self.assign_vars(node.name)
|
|
||||||
self.visit_nodes(node.body)
|
|
||||||
|
|
||||||
visit_ExceptHandler = visit_excepthandler # in 2.6, this was CamelCased
|
|
||||||
|
|
||||||
def flatten(self, nodes):
|
|
||||||
if isinstance(nodes, ast.Attribute):
|
|
||||||
self.visit(nodes)
|
|
||||||
return []
|
|
||||||
elif isinstance(nodes, ast.Subscript):
|
|
||||||
self.visit(nodes.value)
|
|
||||||
self.visit(nodes.slice)
|
|
||||||
return []
|
|
||||||
elif isinstance(nodes, ast.Name):
|
|
||||||
return [nodes]
|
|
||||||
elif isinstance(nodes, (ast.Tuple, ast.List)):
|
|
||||||
return self.flatten(nodes.elts)
|
|
||||||
|
|
||||||
flattened_nodes = []
|
|
||||||
for node in nodes:
|
|
||||||
if hasattr(node, 'elts'):
|
|
||||||
flattened_nodes += self.flatten(node.elts)
|
|
||||||
elif node is not None:
|
|
||||||
flattened_nodes += self.flatten(node)
|
|
||||||
|
|
||||||
return flattened_nodes
|
|
||||||
|
|
||||||
def add_binding(self, node, value, report_redef=True):
|
|
||||||
line, col, scope, name = node.lineno, node.col_offset, self.scope, value.name
|
|
||||||
|
|
||||||
# Check for a redefined function
|
|
||||||
func = scope.get(name)
|
|
||||||
if (isinstance(func, FunctionDefinition) and isinstance(value, FunctionDefinition)):
|
|
||||||
# Property-decorated functions (@x.setter) should have duplicate names
|
|
||||||
if not value._property_decorator:
|
|
||||||
self.report(messages.RedefinedFunction, line, name, func.source.lineno)
|
|
||||||
|
|
||||||
# Check for redefining an unused import
|
|
||||||
if report_redef and not isinstance(scope, ClassScope):
|
|
||||||
for up_scope in self.scope_stack[::-1]:
|
|
||||||
upval = up_scope.get(name)
|
|
||||||
if isinstance(upval, Importation) and not upval.used:
|
|
||||||
self.report(messages.RedefinedWhileUnused, line, col, name, upval.source.lineno)
|
|
||||||
|
|
||||||
# Check for "del undefined_name"
|
|
||||||
if isinstance(value, UnBinding):
|
|
||||||
try:
|
|
||||||
del scope[name]
|
|
||||||
except KeyError:
|
|
||||||
self.report(messages.UndefinedName, line, col, name)
|
|
||||||
else:
|
|
||||||
scope[name] = value
|
|
||||||
|
|
||||||
def visit(self, node):
|
|
||||||
if not isinstance(node, allowed_before_future):
|
|
||||||
self.futures_allowed = False
|
|
||||||
|
|
||||||
return super(Checker, self).visit(node)
|
|
||||||
|
|
||||||
def visit_nodes(self, nodes):
|
|
||||||
try:
|
|
||||||
nodes = list(getattr(nodes, 'elts', nodes))
|
|
||||||
except TypeError:
|
|
||||||
nodes = [nodes]
|
|
||||||
|
|
||||||
for node in nodes:
|
|
||||||
self.visit(node)
|
|
||||||
|
|
||||||
def is_builtin(self, name):
|
|
||||||
if hasattr(__builtin__, name):
|
|
||||||
return True
|
|
||||||
if name in defined_names:
|
|
||||||
return True
|
|
||||||
if name in self.builtins:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,75 +3,94 @@
|
|||||||
class Message(object):
|
class Message(object):
|
||||||
message = ''
|
message = ''
|
||||||
message_args = ()
|
message_args = ()
|
||||||
def __init__(self, filename, lineno, col = None):
|
def __init__(self, filename, loc, use_column=True):
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.lineno = lineno
|
self.lineno = loc.lineno
|
||||||
self.col = col
|
self.col = getattr(loc, 'col_offset', None) if use_column else None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.col is not None:
|
|
||||||
return '%s:%s(%d): %s' % (self.filename, self.lineno, self.col, self.message % self.message_args)
|
|
||||||
else:
|
|
||||||
return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args)
|
return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args)
|
||||||
|
|
||||||
|
|
||||||
class UnusedImport(Message):
|
class UnusedImport(Message):
|
||||||
message = '%r imported but unused'
|
message = '%r imported but unused'
|
||||||
def __init__(self, filename, lineno, name):
|
def __init__(self, filename, loc, name):
|
||||||
Message.__init__(self, filename, lineno)
|
Message.__init__(self, filename, loc, use_column=False)
|
||||||
self.message_args = (name,)
|
self.message_args = (name,)
|
||||||
|
|
||||||
|
|
||||||
class RedefinedWhileUnused(Message):
|
class RedefinedWhileUnused(Message):
|
||||||
message = 'redefinition of unused %r from line %r'
|
message = 'redefinition of unused %r from line %r'
|
||||||
def __init__(self, filename, lineno, col, name, orig_lineno):
|
def __init__(self, filename, loc, name, orig_loc):
|
||||||
Message.__init__(self, filename, lineno)
|
Message.__init__(self, filename, loc)
|
||||||
self.message_args = (name, orig_lineno)
|
self.message_args = (name, orig_loc.lineno)
|
||||||
|
|
||||||
|
|
||||||
class ImportShadowedByLoopVar(Message):
|
class ImportShadowedByLoopVar(Message):
|
||||||
message = 'import %r from line %r shadowed by loop variable'
|
message = 'import %r from line %r shadowed by loop variable'
|
||||||
def __init__(self, filename, lineno, col, name, orig_lineno):
|
def __init__(self, filename, loc, name, orig_loc):
|
||||||
Message.__init__(self, filename, lineno, col)
|
Message.__init__(self, filename, loc)
|
||||||
self.message_args = (name, orig_lineno)
|
self.message_args = (name, orig_loc.lineno)
|
||||||
|
|
||||||
|
|
||||||
class ImportStarUsed(Message):
|
class ImportStarUsed(Message):
|
||||||
message = "'from %s import *' used; unable to detect undefined names"
|
message = "'from %s import *' used; unable to detect undefined names"
|
||||||
def __init__(self, filename, lineno, col, modname):
|
def __init__(self, filename, loc, modname):
|
||||||
Message.__init__(self, filename, lineno, col)
|
Message.__init__(self, filename, loc)
|
||||||
self.message_args = (modname,)
|
self.message_args = (modname,)
|
||||||
|
|
||||||
|
|
||||||
class UndefinedName(Message):
|
class UndefinedName(Message):
|
||||||
message = 'undefined name %r'
|
message = 'undefined name %r'
|
||||||
def __init__(self, filename, lineno, col, name):
|
def __init__(self, filename, loc, name):
|
||||||
Message.__init__(self, filename, lineno, col)
|
Message.__init__(self, filename, loc)
|
||||||
self.message_args = (name,)
|
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):
|
class UndefinedLocal(Message):
|
||||||
message = "local variable %r (defined in enclosing scope on line %r) referenced before assignment"
|
message = "local variable %r (defined in enclosing scope on line %r) referenced before assignment"
|
||||||
def __init__(self, filename, lineno, col, name, orig_lineno, orig_col):
|
def __init__(self, filename, loc, name, orig_loc):
|
||||||
Message.__init__(self, filename, lineno)
|
Message.__init__(self, filename, loc)
|
||||||
self.message_args = (name, orig_lineno)
|
self.message_args = (name, orig_loc.lineno)
|
||||||
|
|
||||||
|
|
||||||
class DuplicateArgument(Message):
|
class DuplicateArgument(Message):
|
||||||
message = 'duplicate argument %r in function definition'
|
message = 'duplicate argument %r in function definition'
|
||||||
def __init__(self, filename, lineno, col, name):
|
def __init__(self, filename, loc, name):
|
||||||
Message.__init__(self, filename, lineno, col)
|
Message.__init__(self, filename, loc)
|
||||||
self.message_args = (name,)
|
self.message_args = (name,)
|
||||||
|
|
||||||
|
|
||||||
class RedefinedFunction(Message):
|
class RedefinedFunction(Message):
|
||||||
message = 'redefinition of function %r from line %r'
|
message = 'redefinition of function %r from line %r'
|
||||||
def __init__(self, filename, lineno, name, orig_lineno):
|
def __init__(self, filename, loc, name, orig_loc):
|
||||||
Message.__init__(self, filename, lineno)
|
Message.__init__(self, filename, loc)
|
||||||
self.message_args = (name, orig_lineno)
|
self.message_args = (name, orig_loc.lineno)
|
||||||
|
|
||||||
|
|
||||||
class LateFutureImport(Message):
|
class LateFutureImport(Message):
|
||||||
message = 'future import(s) %r after other statements'
|
message = 'future import(s) %r after other statements'
|
||||||
def __init__(self, filename, lineno, col, names):
|
def __init__(self, filename, loc, names):
|
||||||
Message.__init__(self, filename, lineno)
|
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,)
|
self.message_args = (names,)
|
||||||
|
|||||||
@@ -3,29 +3,55 @@
|
|||||||
Implementation of the command-line I{pyflakes} tool.
|
Implementation of the command-line I{pyflakes} tool.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import _ast
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import _ast
|
||||||
|
|
||||||
checker = __import__('pyflakes.checker').checker
|
checker = __import__('pyflakes.checker').checker
|
||||||
|
|
||||||
def check(codeString, filename):
|
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:
|
try:
|
||||||
tree = compile(codeString, filename, 'exec', _ast.PyCF_ONLY_AST)
|
tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
|
||||||
except (SyntaxError, IndentationError):
|
except SyntaxError, value:
|
||||||
value = sys.exc_info()[1]
|
msg = value.args[0]
|
||||||
try:
|
|
||||||
(lineno, offset, line) = value[1][1:]
|
(lineno, offset, text) = value.lineno, value.offset, value.text
|
||||||
except IndexError:
|
|
||||||
print >> sys.stderr, 'could not compile %r' % (filename,)
|
# If there's an encoding problem with the file, the text is None.
|
||||||
return 1
|
if text is None:
|
||||||
if line.endswith("\n"):
|
# Avoid using msg, since for the only known case, it contains a
|
||||||
line = line[:-1]
|
# bogus message that claims the encoding the file declared was
|
||||||
print >> sys.stderr, '%s:%d: could not compile' % (filename, lineno)
|
# 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
|
print >> sys.stderr, line
|
||||||
print >> sys.stderr, " " * (offset-2), "^"
|
|
||||||
|
if offset is not None:
|
||||||
|
print >> sys.stderr, " " * offset, "^"
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
|
# Okay, it's syntactically valid. Now check it.
|
||||||
w = checker.Checker(tree, filename)
|
w = checker.Checker(tree, filename)
|
||||||
w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
|
w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
|
||||||
for warning in w.messages:
|
for warning in w.messages:
|
||||||
@@ -39,12 +65,13 @@ def checkPath(filename):
|
|||||||
|
|
||||||
@return: the number of warnings printed
|
@return: the number of warnings printed
|
||||||
"""
|
"""
|
||||||
if os.path.exists(filename):
|
try:
|
||||||
return check(file(filename, 'U').read() + '\n', filename)
|
return check(file(filename, 'U').read() + '\n', filename)
|
||||||
else:
|
except IOError, msg:
|
||||||
print >> sys.stderr, '%s: no such file' % (filename,)
|
print >> sys.stderr, "%s: %s" % (filename, msg.args[1])
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
warnings = 0
|
warnings = 0
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
|
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import _ast
|
||||||
|
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
|
|
||||||
from pyflakes import checker, ast
|
from pyflakes import checker
|
||||||
|
|
||||||
|
|
||||||
class Test(unittest.TestCase):
|
class Test(unittest.TestCase):
|
||||||
|
|
||||||
def flakes(self, input, *expectedOutputs):
|
def flakes(self, input, *expectedOutputs, **kw):
|
||||||
w = checker.Checker(ast.parse(textwrap.dedent(input)))
|
ast = compile(textwrap.dedent(input), "<test>", "exec",
|
||||||
|
_ast.PyCF_ONLY_AST)
|
||||||
|
w = checker.Checker(ast, **kw)
|
||||||
outputs = [type(o) for o in w.messages]
|
outputs = [type(o) for o in w.messages]
|
||||||
expectedOutputs = list(expectedOutputs)
|
expectedOutputs = list(expectedOutputs)
|
||||||
outputs.sort()
|
outputs.sort()
|
||||||
|
|||||||
@@ -51,6 +51,19 @@ class Test(harness.Test):
|
|||||||
pass
|
pass
|
||||||
''', m.RedefinedWhileUnused)
|
''', 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):
|
def test_redefinedInClass(self):
|
||||||
"""
|
"""
|
||||||
Test that shadowing a global with a class attribute does not produce a
|
Test that shadowing a global with a class attribute does not produce a
|
||||||
@@ -377,9 +390,55 @@ class Test(harness.Test):
|
|||||||
def test_importStar(self):
|
def test_importStar(self):
|
||||||
self.flakes('from fu import *', m.ImportStarUsed)
|
self.flakes('from fu import *', m.ImportStarUsed)
|
||||||
|
|
||||||
|
|
||||||
def test_packageImport(self):
|
def test_packageImport(self):
|
||||||
self.flakes('import fu.bar; fu.bar')
|
"""
|
||||||
test_packageImport.todo = "this has been hacked to treat 'import fu.bar' as just 'import fu'"
|
If a dotted name is imported and used, no warning is reported.
|
||||||
|
"""
|
||||||
|
self.flakes('''
|
||||||
|
import fu.bar
|
||||||
|
fu.bar
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
def test_unusedPackageImport(self):
|
||||||
|
"""
|
||||||
|
If a dotted name is imported and not used, an unused import warning is
|
||||||
|
reported.
|
||||||
|
"""
|
||||||
|
self.flakes('import fu.bar', m.UnusedImport)
|
||||||
|
|
||||||
|
|
||||||
|
def test_duplicateSubmoduleImport(self):
|
||||||
|
"""
|
||||||
|
If a submodule of a package is imported twice, an unused import warning
|
||||||
|
and a redefined while unused warning are reported.
|
||||||
|
"""
|
||||||
|
self.flakes('''
|
||||||
|
import fu.bar, fu.bar
|
||||||
|
fu.bar
|
||||||
|
''', m.RedefinedWhileUnused)
|
||||||
|
self.flakes('''
|
||||||
|
import fu.bar
|
||||||
|
import fu.bar
|
||||||
|
fu.bar
|
||||||
|
''', m.RedefinedWhileUnused)
|
||||||
|
|
||||||
|
|
||||||
|
def test_differentSubmoduleImport(self):
|
||||||
|
"""
|
||||||
|
If two different submodules of a package are imported, no duplicate
|
||||||
|
import warning is reported for the package.
|
||||||
|
"""
|
||||||
|
self.flakes('''
|
||||||
|
import fu.bar, fu.baz
|
||||||
|
fu.bar, fu.baz
|
||||||
|
''')
|
||||||
|
self.flakes('''
|
||||||
|
import fu.bar
|
||||||
|
import fu.baz
|
||||||
|
fu.bar, fu.baz
|
||||||
|
''')
|
||||||
|
|
||||||
def test_assignRHSFirst(self):
|
def test_assignRHSFirst(self):
|
||||||
self.flakes('import fu; fu = fu')
|
self.flakes('import fu; fu = fu')
|
||||||
@@ -401,6 +460,7 @@ class Test(harness.Test):
|
|||||||
import fu
|
import fu
|
||||||
def a():
|
def a():
|
||||||
fu = 3
|
fu = 3
|
||||||
|
return fu
|
||||||
fu
|
fu
|
||||||
''')
|
''')
|
||||||
|
|
||||||
@@ -431,11 +491,6 @@ class Test(harness.Test):
|
|||||||
''')
|
''')
|
||||||
test_importingForImportError.todo = ''
|
test_importingForImportError.todo = ''
|
||||||
|
|
||||||
def test_explicitlyPublic(self):
|
|
||||||
'''imports mentioned in __all__ are not unused'''
|
|
||||||
self.flakes('import fu; __all__ = ["fu"]')
|
|
||||||
test_explicitlyPublic.todo = "this would require importing the module or doing smarter parsing"
|
|
||||||
|
|
||||||
def test_importedInClass(self):
|
def test_importedInClass(self):
|
||||||
'''Imports in class scope can be used through self'''
|
'''Imports in class scope can be used through self'''
|
||||||
self.flakes('''
|
self.flakes('''
|
||||||
@@ -449,6 +504,10 @@ class Test(harness.Test):
|
|||||||
def test_futureImport(self):
|
def test_futureImport(self):
|
||||||
'''__future__ is special'''
|
'''__future__ is special'''
|
||||||
self.flakes('from __future__ import division')
|
self.flakes('from __future__ import division')
|
||||||
|
self.flakes('''
|
||||||
|
"docstring is allowed before future import"
|
||||||
|
from __future__ import division
|
||||||
|
''')
|
||||||
|
|
||||||
def test_futureImportFirst(self):
|
def test_futureImportFirst(self):
|
||||||
"""
|
"""
|
||||||
@@ -458,15 +517,82 @@ class Test(harness.Test):
|
|||||||
x = 5
|
x = 5
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
''', m.LateFutureImport)
|
''', m.LateFutureImport)
|
||||||
|
self.flakes('''
|
||||||
|
from foo import bar
|
||||||
|
from __future__ import division
|
||||||
|
bar
|
||||||
|
''', m.LateFutureImport)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Python24Tests(harness.Test):
|
class TestSpecialAll(harness.Test):
|
||||||
"""
|
"""
|
||||||
Tests for checking of syntax which is valid in Python 2.4 and newer.
|
Tests for suppression of unused import warnings by C{__all__}.
|
||||||
"""
|
"""
|
||||||
if version_info < (2, 4):
|
def test_ignoredInFunction(self):
|
||||||
skip = "Python 2.4 required for generator expression and decorator tests."
|
"""
|
||||||
|
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):
|
def test_usedInGenExp(self):
|
||||||
@@ -510,3 +636,38 @@ class Python24Tests(harness.Test):
|
|||||||
def f():
|
def f():
|
||||||
return "hello"
|
return "hello"
|
||||||
''', m.UndefinedName)
|
''', 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,4 +1,4 @@
|
|||||||
# (c) 2005-2008 Divmod, Inc.
|
# (c) 2005-2010 Divmod, Inc.
|
||||||
# See LICENSE file for details
|
# See LICENSE file for details
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -74,6 +74,259 @@ class Test(harness.Test):
|
|||||||
self.flakes('+1')
|
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):
|
class Python25Test(harness.Test):
|
||||||
"""
|
"""
|
||||||
@@ -117,6 +370,45 @@ class Python25Test(harness.Test):
|
|||||||
''')
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
def test_withStatementAttributeName(self):
|
||||||
|
"""
|
||||||
|
No warnings are emitted for using an attribute as the target of a
|
||||||
|
C{with} statement.
|
||||||
|
"""
|
||||||
|
self.flakes('''
|
||||||
|
from __future__ import with_statement
|
||||||
|
import foo
|
||||||
|
with open('foo') as foo.bar:
|
||||||
|
pass
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
def test_withStatementSubscript(self):
|
||||||
|
"""
|
||||||
|
No warnings are emitted for using a subscript as the target of a
|
||||||
|
C{with} statement.
|
||||||
|
"""
|
||||||
|
self.flakes('''
|
||||||
|
from __future__ import with_statement
|
||||||
|
import foo
|
||||||
|
with open('foo') as foo[0]:
|
||||||
|
pass
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
def test_withStatementSubscriptUndefined(self):
|
||||||
|
"""
|
||||||
|
An undefined name warning is emitted if the subscript used as the
|
||||||
|
target of a C{with} statement is not defined.
|
||||||
|
"""
|
||||||
|
self.flakes('''
|
||||||
|
from __future__ import with_statement
|
||||||
|
import foo
|
||||||
|
with open('foo') as foo[bar]:
|
||||||
|
pass
|
||||||
|
''', m.UndefinedName)
|
||||||
|
|
||||||
|
|
||||||
def test_withStatementTupleNames(self):
|
def test_withStatementTupleNames(self):
|
||||||
"""
|
"""
|
||||||
No warnings are emitted for using any of the tuple of names defined by
|
No warnings are emitted for using any of the tuple of names defined by
|
||||||
@@ -130,6 +422,36 @@ class Python25Test(harness.Test):
|
|||||||
''')
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
def test_withStatementListNames(self):
|
||||||
|
"""
|
||||||
|
No warnings are emitted for using any of the list of names defined by a
|
||||||
|
C{with} statement within the suite or afterwards.
|
||||||
|
"""
|
||||||
|
self.flakes('''
|
||||||
|
from __future__ import with_statement
|
||||||
|
with open('foo') as [bar, baz]:
|
||||||
|
bar, baz
|
||||||
|
bar, baz
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
def test_withStatementComplicatedTarget(self):
|
||||||
|
"""
|
||||||
|
If the target of a C{with} statement uses any or all of the valid forms
|
||||||
|
for that part of the grammar (See
|
||||||
|
U{http://docs.python.org/reference/compound_stmts.html#the-with-statement}),
|
||||||
|
the names involved are checked both for definedness and any bindings
|
||||||
|
created are respected in the suite of the statement and afterwards.
|
||||||
|
"""
|
||||||
|
self.flakes('''
|
||||||
|
from __future__ import with_statement
|
||||||
|
c = d = e = g = h = i = None
|
||||||
|
with open('foo') as [(a, b), c[d], e.f, g[h:i]]:
|
||||||
|
a, b, c, d, e, g, h, i
|
||||||
|
a, b, c, d, e, g, h, i
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
def test_withStatementSingleNameUndefined(self):
|
def test_withStatementSingleNameUndefined(self):
|
||||||
"""
|
"""
|
||||||
An undefined name warning is emitted if the name first defined by a
|
An undefined name warning is emitted if the name first defined by a
|
||||||
@@ -226,9 +548,28 @@ class Python25Test(harness.Test):
|
|||||||
pass
|
pass
|
||||||
''', m.UndefinedName)
|
''', m.UndefinedName)
|
||||||
|
|
||||||
def test_listNestedListComprehension(self):
|
|
||||||
|
|
||||||
|
class Python27Test(harness.Test):
|
||||||
|
"""
|
||||||
|
Tests for checking of syntax only available in Python 2.7 and newer.
|
||||||
|
"""
|
||||||
|
if version_info < (2, 7):
|
||||||
|
skip = "Python 2.7 required for dict/set comprehension tests"
|
||||||
|
|
||||||
|
def test_dictComprehension(self):
|
||||||
|
"""
|
||||||
|
Dict comprehensions are properly handled.
|
||||||
|
"""
|
||||||
self.flakes('''
|
self.flakes('''
|
||||||
root = [['213', '123'], ['4354']]
|
a = {1: x for x in range(10)}
|
||||||
foo = [int(c) for group in root for c in group]
|
|
||||||
''')
|
''')
|
||||||
|
|
||||||
|
def test_setComprehensionAndLiteral(self):
|
||||||
|
"""
|
||||||
|
Set comprehensions are properly handled.
|
||||||
|
"""
|
||||||
|
self.flakes('''
|
||||||
|
a = {1, 2, 3}
|
||||||
|
b = {x for x in range(10)}
|
||||||
|
''')
|
||||||
|
|||||||
@@ -44,5 +44,142 @@ class CheckTests(TestCase):
|
|||||||
"""
|
"""
|
||||||
err = StringIO()
|
err = StringIO()
|
||||||
count = withStderrTo(err, lambda: checkPath('extremo'))
|
count = withStderrTo(err, lambda: checkPath('extremo'))
|
||||||
self.assertEquals(err.getvalue(), 'extremo: no such file\n')
|
self.assertEquals(err.getvalue(), 'extremo: No such file or directory\n')
|
||||||
self.assertEquals(count, 1)
|
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,7 +1,9 @@
|
|||||||
|
|
||||||
from sys import version_info
|
from _ast import PyCF_ONLY_AST
|
||||||
|
|
||||||
from pyflakes import messages as m
|
from twisted.trial.unittest import TestCase
|
||||||
|
|
||||||
|
from pyflakes import messages as m, checker
|
||||||
from pyflakes.test import harness
|
from pyflakes.test import harness
|
||||||
|
|
||||||
|
|
||||||
@@ -24,9 +26,40 @@ class Test(harness.Test):
|
|||||||
def test_builtins(self):
|
def test_builtins(self):
|
||||||
self.flakes('range(10)')
|
self.flakes('range(10)')
|
||||||
|
|
||||||
def test_magic_globals(self):
|
|
||||||
|
def test_magicGlobalsFile(self):
|
||||||
|
"""
|
||||||
|
Use of the C{__file__} magic global should not emit an undefined name
|
||||||
|
warning.
|
||||||
|
"""
|
||||||
self.flakes('__file__')
|
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):
|
def test_globalImportStar(self):
|
||||||
'''Can't find undefined names with import *'''
|
'''Can't find undefined names with import *'''
|
||||||
self.flakes('from fu import *; bar', m.ImportStarUsed)
|
self.flakes('from fu import *; bar', m.ImportStarUsed)
|
||||||
@@ -54,6 +87,16 @@ class Test(harness.Test):
|
|||||||
''')
|
''')
|
||||||
test_definedByGlobal.todo = ''
|
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):
|
def test_del(self):
|
||||||
'''del deletes bindings'''
|
'''del deletes bindings'''
|
||||||
self.flakes('a = 1; del a; a', m.UndefinedName)
|
self.flakes('a = 1; del a; a', m.UndefinedName)
|
||||||
@@ -91,6 +134,7 @@ class Test(harness.Test):
|
|||||||
def fun():
|
def fun():
|
||||||
a
|
a
|
||||||
a = 2
|
a = 2
|
||||||
|
return a
|
||||||
''', m.UndefinedLocal)
|
''', m.UndefinedLocal)
|
||||||
|
|
||||||
def test_laterRedefinedGlobalFromNestedScope2(self):
|
def test_laterRedefinedGlobalFromNestedScope2(self):
|
||||||
@@ -106,6 +150,26 @@ class Test(harness.Test):
|
|||||||
def fun2():
|
def fun2():
|
||||||
a
|
a
|
||||||
a = 2
|
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)
|
''', m.UndefinedLocal)
|
||||||
|
|
||||||
|
|
||||||
@@ -124,6 +188,9 @@ class Test(harness.Test):
|
|||||||
def c():
|
def c():
|
||||||
x
|
x
|
||||||
x = 3
|
x = 3
|
||||||
|
return x
|
||||||
|
return x
|
||||||
|
return x
|
||||||
''', m.UndefinedLocal).messages[0]
|
''', m.UndefinedLocal).messages[0]
|
||||||
self.assertEqual(exc.message_args, ('x', 5))
|
self.assertEqual(exc.message_args, ('x', 5))
|
||||||
|
|
||||||
@@ -139,6 +206,8 @@ class Test(harness.Test):
|
|||||||
def fun2():
|
def fun2():
|
||||||
a
|
a
|
||||||
a = 1
|
a = 1
|
||||||
|
return a
|
||||||
|
return a
|
||||||
''', m.UndefinedLocal)
|
''', m.UndefinedLocal)
|
||||||
|
|
||||||
def test_nestedClass(self):
|
def test_nestedClass(self):
|
||||||
@@ -161,18 +230,16 @@ class Test(harness.Test):
|
|||||||
class C:
|
class C:
|
||||||
bar = foo
|
bar = foo
|
||||||
foo = 456
|
foo = 456
|
||||||
|
return foo
|
||||||
f()
|
f()
|
||||||
''', m.UndefinedName)
|
''', m.UndefinedName)
|
||||||
|
|
||||||
|
def test_definedAsStarArgs(self):
|
||||||
|
'''star and double-star arg names are defined'''
|
||||||
class Python24Test(harness.Test):
|
self.flakes('''
|
||||||
"""
|
def f(a, *b, **c):
|
||||||
Tests for checking of syntax which is valid in Python 2.4 and newer.
|
print a, b, c
|
||||||
"""
|
''')
|
||||||
if version_info < (2, 4):
|
|
||||||
skip = "Python 2.4 required for generator expression tests."
|
|
||||||
|
|
||||||
def test_definedInGenExp(self):
|
def test_definedInGenExp(self):
|
||||||
"""
|
"""
|
||||||
@@ -180,3 +247,19 @@ class Python24Test(harness.Test):
|
|||||||
warnings.
|
warnings.
|
||||||
"""
|
"""
|
||||||
self.flakes('(a for a in xrange(10) if a)')
|
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,19 +1,28 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# (c) 2005 Divmod, Inc. See LICENSE file for details
|
# (c) 2005-2009 Divmod, Inc. See LICENSE file for details
|
||||||
|
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="pyflakes",
|
name="pyflakes",
|
||||||
license="MIT",
|
license="MIT",
|
||||||
version="0.2.1",
|
version="0.4.0",
|
||||||
description="passive checker of Python programs",
|
description="passive checker of Python programs",
|
||||||
author="Phil Frost",
|
author="Phil Frost",
|
||||||
maintainer="Moe Aboulkheir",
|
maintainer="Moe Aboulkheir",
|
||||||
maintainer_email="moe@divmod.com",
|
maintainer_email="moe@divmod.com",
|
||||||
url="http://www.divmod.org/projects/pyflakes",
|
url="http://www.divmod.org/trac/wiki/DivmodPyflakes",
|
||||||
packages=["pyflakes", "pyflakes.scripts"],
|
packages=["pyflakes", "pyflakes.scripts", "pyflakes.test"],
|
||||||
scripts=["bin/pyflakes"],
|
scripts=["bin/pyflakes"],
|
||||||
long_description="""Pyflakes is program to analyze Python programs and detect various errors. It
|
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
|
works by parsing the source file, not importing it, so it is safe to use on
|
||||||
modules with side effects. It's also much faster.""")
|
modules with side effects. It's also much faster.""",
|
||||||
|
classifiers=[
|
||||||
|
"Development Status :: 6 - Mature",
|
||||||
|
"Environment :: Console",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Topic :: Software Development",
|
||||||
|
"Topic :: Utilities",
|
||||||
|
])
|
||||||
|
|||||||
170
plugin/acp.vim
Normal file
170
plugin/acp.vim
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
"=============================================================================
|
||||||
|
" Copyright (c) 2007-2009 Takeshi NISHIDA
|
||||||
|
"
|
||||||
|
" GetLatestVimScripts: 1879 1 :AutoInstall: AutoComplPop
|
||||||
|
"=============================================================================
|
||||||
|
" LOAD GUARD {{{1
|
||||||
|
|
||||||
|
if exists('g:loaded_acp')
|
||||||
|
finish
|
||||||
|
elseif v:version < 702
|
||||||
|
echoerr 'AutoComplPop does not support this version of vim (' . v:version . ').'
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
let g:loaded_acp = 1
|
||||||
|
|
||||||
|
" }}}1
|
||||||
|
"=============================================================================
|
||||||
|
" FUNCTION: {{{1
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:defineOption(name, default)
|
||||||
|
if !exists(a:name)
|
||||||
|
let {a:name} = a:default
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
"
|
||||||
|
function s:makeDefaultBehavior()
|
||||||
|
let behavs = {
|
||||||
|
\ '*' : [],
|
||||||
|
\ 'ruby' : [],
|
||||||
|
\ 'python' : [],
|
||||||
|
\ 'perl' : [],
|
||||||
|
\ 'xml' : [],
|
||||||
|
\ 'html' : [],
|
||||||
|
\ 'xhtml' : [],
|
||||||
|
\ 'css' : [],
|
||||||
|
\ }
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
if !empty(g:acp_behaviorUserDefinedFunction) &&
|
||||||
|
\ !empty(g:acp_behaviorUserDefinedMeets)
|
||||||
|
for key in keys(behavs)
|
||||||
|
call add(behavs[key], {
|
||||||
|
\ 'command' : "\<C-x>\<C-u>",
|
||||||
|
\ 'completefunc' : g:acp_behaviorUserDefinedFunction,
|
||||||
|
\ 'meets' : g:acp_behaviorUserDefinedMeets,
|
||||||
|
\ 'repeat' : 0,
|
||||||
|
\ })
|
||||||
|
endfor
|
||||||
|
endif
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
for key in keys(behavs)
|
||||||
|
call add(behavs[key], {
|
||||||
|
\ 'command' : "\<C-x>\<C-u>",
|
||||||
|
\ 'completefunc' : 'acp#completeSnipmate',
|
||||||
|
\ 'meets' : 'acp#meetsForSnipmate',
|
||||||
|
\ 'onPopupClose' : 'acp#onPopupCloseSnipmate',
|
||||||
|
\ 'repeat' : 0,
|
||||||
|
\ })
|
||||||
|
endfor
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
for key in keys(behavs)
|
||||||
|
call add(behavs[key], {
|
||||||
|
\ 'command' : g:acp_behaviorKeywordCommand,
|
||||||
|
\ 'meets' : 'acp#meetsForKeyword',
|
||||||
|
\ 'repeat' : 0,
|
||||||
|
\ })
|
||||||
|
endfor
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
for key in keys(behavs)
|
||||||
|
call add(behavs[key], {
|
||||||
|
\ 'command' : "\<C-x>\<C-f>",
|
||||||
|
\ 'meets' : 'acp#meetsForFile',
|
||||||
|
\ 'repeat' : 1,
|
||||||
|
\ })
|
||||||
|
endfor
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
call add(behavs.ruby, {
|
||||||
|
\ 'command' : "\<C-x>\<C-o>",
|
||||||
|
\ 'meets' : 'acp#meetsForRubyOmni',
|
||||||
|
\ 'repeat' : 0,
|
||||||
|
\ })
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
call add(behavs.python, {
|
||||||
|
\ 'command' : "\<C-x>\<C-o>",
|
||||||
|
\ 'meets' : 'acp#meetsForPythonOmni',
|
||||||
|
\ 'repeat' : 0,
|
||||||
|
\ })
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
call add(behavs.perl, {
|
||||||
|
\ 'command' : "\<C-x>\<C-o>",
|
||||||
|
\ 'meets' : 'acp#meetsForPerlOmni',
|
||||||
|
\ 'repeat' : 0,
|
||||||
|
\ })
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
call add(behavs.xml, {
|
||||||
|
\ 'command' : "\<C-x>\<C-o>",
|
||||||
|
\ 'meets' : 'acp#meetsForXmlOmni',
|
||||||
|
\ 'repeat' : 1,
|
||||||
|
\ })
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
call add(behavs.html, {
|
||||||
|
\ 'command' : "\<C-x>\<C-o>",
|
||||||
|
\ 'meets' : 'acp#meetsForHtmlOmni',
|
||||||
|
\ 'repeat' : 1,
|
||||||
|
\ })
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
call add(behavs.xhtml, {
|
||||||
|
\ 'command' : "\<C-x>\<C-o>",
|
||||||
|
\ 'meets' : 'acp#meetsForHtmlOmni',
|
||||||
|
\ 'repeat' : 1,
|
||||||
|
\ })
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
call add(behavs.css, {
|
||||||
|
\ 'command' : "\<C-x>\<C-o>",
|
||||||
|
\ 'meets' : 'acp#meetsForCssOmni',
|
||||||
|
\ 'repeat' : 0,
|
||||||
|
\ })
|
||||||
|
"---------------------------------------------------------------------------
|
||||||
|
return behavs
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" }}}1
|
||||||
|
"=============================================================================
|
||||||
|
" INITIALIZATION {{{1
|
||||||
|
|
||||||
|
"-----------------------------------------------------------------------------
|
||||||
|
call s:defineOption('g:acp_enableAtStartup', 1)
|
||||||
|
call s:defineOption('g:acp_mappingDriven', 0)
|
||||||
|
call s:defineOption('g:acp_ignorecaseOption', 1)
|
||||||
|
call s:defineOption('g:acp_completeOption', '.,w,b,k')
|
||||||
|
call s:defineOption('g:acp_completeoptPreview', 0)
|
||||||
|
call s:defineOption('g:acp_behaviorUserDefinedFunction', '')
|
||||||
|
call s:defineOption('g:acp_behaviorUserDefinedMeets', '')
|
||||||
|
call s:defineOption('g:acp_behaviorSnipmateLength', -1)
|
||||||
|
call s:defineOption('g:acp_behaviorKeywordCommand', "\<C-n>")
|
||||||
|
call s:defineOption('g:acp_behaviorKeywordLength', 2)
|
||||||
|
call s:defineOption('g:acp_behaviorKeywordIgnores', [])
|
||||||
|
call s:defineOption('g:acp_behaviorFileLength', 0)
|
||||||
|
call s:defineOption('g:acp_behaviorRubyOmniMethodLength', 0)
|
||||||
|
call s:defineOption('g:acp_behaviorRubyOmniSymbolLength', 1)
|
||||||
|
call s:defineOption('g:acp_behaviorPythonOmniLength', 0)
|
||||||
|
call s:defineOption('g:acp_behaviorPerlOmniLength', -1)
|
||||||
|
call s:defineOption('g:acp_behaviorXmlOmniLength', 0)
|
||||||
|
call s:defineOption('g:acp_behaviorHtmlOmniLength', 0)
|
||||||
|
call s:defineOption('g:acp_behaviorCssOmniPropertyLength', 1)
|
||||||
|
call s:defineOption('g:acp_behaviorCssOmniValueLength', 0)
|
||||||
|
call s:defineOption('g:acp_behavior', {})
|
||||||
|
"-----------------------------------------------------------------------------
|
||||||
|
call extend(g:acp_behavior, s:makeDefaultBehavior(), 'keep')
|
||||||
|
"-----------------------------------------------------------------------------
|
||||||
|
command! -bar -narg=0 AcpEnable call acp#enable()
|
||||||
|
command! -bar -narg=0 AcpDisable call acp#disable()
|
||||||
|
command! -bar -narg=0 AcpLock call acp#lock()
|
||||||
|
command! -bar -narg=0 AcpUnlock call acp#unlock()
|
||||||
|
"-----------------------------------------------------------------------------
|
||||||
|
" legacy commands
|
||||||
|
command! -bar -narg=0 AutoComplPopEnable AcpEnable
|
||||||
|
command! -bar -narg=0 AutoComplPopDisable AcpDisable
|
||||||
|
command! -bar -narg=0 AutoComplPopLock AcpLock
|
||||||
|
command! -bar -narg=0 AutoComplPopUnlock AcpUnlock
|
||||||
|
"-----------------------------------------------------------------------------
|
||||||
|
if g:acp_enableAtStartup
|
||||||
|
AcpEnable
|
||||||
|
endif
|
||||||
|
"-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
" }}}1
|
||||||
|
"=============================================================================
|
||||||
|
" vim: set fdm=marker:
|
||||||
Reference in New Issue
Block a user