Files
2013-04-04 08:54:25 -04:00

266 lines
10 KiB
Python

# -*- coding: utf-8 -*-
# python.py - Lint checking for Python - given filename and contents of the code:
# It provides a list of line numbers to outline and offsets to highlight.
#
# This specific module is part of the SublimeLinter project.
# It is a fork by André Roberge from the original SublimeLint project,
# (c) 2011 Ryan Hileman and licensed under the MIT license.
# URL: http://bochs.info/
#
# The original copyright notices for this file/project follows:
#
# (c) 2005-2008 Divmod, Inc.
# See LICENSE file for details
#
# The LICENSE file is as follows:
#
# Copyright (c) 2005 Divmod, Inc., http://www.divmod.com/
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# TODO:
# * fix regex for variable names inside strings (quotes)
import re
import _ast
import pep8
import pyflakes.checker as pyflakes
from base_linter import BaseLinter
pyflakes.messages.Message.__str__ = lambda self: self.message % self.message_args
CONFIG = {
'language': 'Python'
}
class PythonLintError(pyflakes.messages.Message):
def __init__(self, filename, loc, level, message, message_args, offset=None, text=None):
super(PythonLintError, self).__init__(filename, loc)
self.level = level
self.message = message
self.message_args = message_args
if offset is not None: self.offset = offset
if text is not None: self.text = text
class Pep8Error(PythonLintError):
def __init__(self, filename, loc, offset, code, text):
# PEP 8 Errors are downgraded to "warnings"
super(Pep8Error, self).__init__(filename, loc, 'W', '[W] PEP 8 (%s): %s', (code, text),
offset=offset, text=text)
class Pep8Warning(PythonLintError):
def __init__(self, filename, loc, offset, code, text):
# PEP 8 Warnings are downgraded to "violations"
super(Pep8Warning, self).__init__(filename, loc, 'V', '[V] PEP 8 (%s): %s', (code, text),
offset=offset, text=text)
class OffsetError(PythonLintError):
def __init__(self, filename, loc, text, offset):
super(OffsetError, self).__init__(filename, loc, 'E', '[E] %r', (text,), offset=offset + 1, text=text)
class PythonError(PythonLintError):
def __init__(self, filename, loc, text):
super(PythonError, self).__init__(filename, loc, 'E', '[E] %r', (text,), text=text)
class Linter(BaseLinter):
def pyflakes_check(self, code, filename, ignore=None):
try:
tree = compile(code, filename, "exec", _ast.PyCF_ONLY_AST)
except (SyntaxError, IndentationError), value:
msg = value.args[0]
(lineno, offset, text) = value.lineno, value.offset, value.text
# If there's an encoding problem with the file, the text is None.
if text is None:
# Avoid using msg, since for the only known case, it contains a
# bogus message that claims the encoding the file declared was
# unknown.
if msg.startswith('duplicate argument'):
arg = msg.split('duplicate argument ', 1)[1].split(' ', 1)[0].strip('\'"')
error = pyflakes.messages.DuplicateArgument(filename, lineno, arg)
else:
error = PythonError(filename, lineno, msg)
else:
line = text.splitlines()[-1]
if offset is not None:
offset = offset - (len(text) - len(line))
if offset is not None:
error = OffsetError(filename, lineno, msg, offset)
else:
error = PythonError(filename, lineno, msg)
return [error]
except ValueError, e:
return [PythonError(filename, 0, e.args[0])]
else:
# Okay, it's syntactically valid. Now check it.
if ignore is not None:
old_magic_globals = pyflakes._MAGIC_GLOBALS
pyflakes._MAGIC_GLOBALS += ignore
w = pyflakes.Checker(tree, filename)
if ignore is not None:
pyflakes._MAGIC_GLOBALS = old_magic_globals
return w.messages
def pep8_check(self, code, filename, ignore=None):
messages = []
_lines = code.split('\n')
if _lines:
def report_error(self, line_number, offset, text, check):
code = text[:4]
msg = text[5:]
if pep8.ignore_code(code):
return
elif code.startswith('E'):
messages.append(Pep8Error(filename, line_number, offset, code, msg))
else:
messages.append(Pep8Warning(filename, line_number, offset, code, msg))
pep8.Checker.report_error = report_error
_ignore = ignore + pep8.DEFAULT_IGNORE.split(',')
class FakeOptions:
verbose = 0
select = []
ignore = _ignore
pep8.options = FakeOptions()
pep8.options.physical_checks = pep8.find_checks('physical_line')
pep8.options.logical_checks = pep8.find_checks('logical_line')
pep8.options.max_line_length = pep8.MAX_LINE_LENGTH
pep8.options.counters = dict.fromkeys(pep8.BENCHMARK_KEYS, 0)
good_lines = [l + '\n' for l in _lines]
good_lines[-1] = good_lines[-1].rstrip('\n')
if not good_lines[-1]:
good_lines = good_lines[:-1]
try:
pep8.Checker(filename, good_lines).check_all()
except Exception, e:
print "An exception occured when running pep8 checker: %s" % e
return messages
def built_in_check(self, view, code, filename):
errors = []
if view.settings().get("pep8", True):
errors.extend(self.pep8_check(code, filename, ignore=view.settings().get('pep8_ignore', [])))
pyflakes_ignore = view.settings().get('pyflakes_ignore', None)
pyflakes_disabled = view.settings().get('pyflakes_disabled', False)
if not pyflakes_disabled:
errors.extend(self.pyflakes_check(code, filename, pyflakes_ignore))
return errors
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
def underline_word(lineno, word, underlines):
regex = r'((and|or|not|if|elif|while|in)\s+|[+\-*^%%<>=\(\{{])*\s*(?P<underline>[\w\.]*{0}[\w]*)'.format(re.escape(word))
self.underline_regex(view, lineno, regex, lines, underlines, word)
def underline_import(lineno, word, underlines):
linematch = '(from\s+[\w_\.]+\s+)?import\s+(?P<match>[^#;]+)'
regex = '(^|\s+|,\s*|as\s+)(?P<underline>[\w]*{0}[\w]*)'.format(re.escape(word))
self.underline_regex(view, lineno, regex, lines, underlines, word, linematch)
def underline_for_var(lineno, word, underlines):
regex = 'for\s+(?P<underline>[\w]*{0}[\w*])'.format(re.escape(word))
self.underline_regex(view, lineno, regex, lines, underlines, word)
def underline_duplicate_argument(lineno, word, underlines):
regex = 'def [\w_]+\(.*?(?P<underline>[\w]*{0}[\w]*)'.format(re.escape(word))
self.underline_regex(view, lineno, regex, lines, underlines, word)
errors.sort(lambda a, b: cmp(a.lineno, b.lineno))
ignoreImportStar = view.settings().get('pyflakes_ignore_import_*', True)
for error in errors:
try:
error_level = error.level
except AttributeError:
error_level = 'W'
if error_level == 'E':
messages = errorMessages
underlines = errorUnderlines
elif error_level == 'V':
messages = violationMessages
underlines = violationUnderlines
elif error_level == 'W':
messages = warningMessages
underlines = warningUnderlines
if isinstance(error, pyflakes.messages.ImportStarUsed) and ignoreImportStar:
continue
self.add_message(error.lineno, lines, str(error), messages)
if isinstance(error, (Pep8Error, Pep8Warning, OffsetError)):
self.underline_range(view, error.lineno, error.offset, underlines)
elif isinstance(error, (pyflakes.messages.RedefinedWhileUnused,
pyflakes.messages.UndefinedName,
pyflakes.messages.UndefinedExport,
pyflakes.messages.UndefinedLocal,
pyflakes.messages.RedefinedFunction,
pyflakes.messages.UnusedVariable)):
underline_word(error.lineno, error.message, underlines)
elif isinstance(error, pyflakes.messages.ImportShadowedByLoopVar):
underline_for_var(error.lineno, error.message, underlines)
elif isinstance(error, pyflakes.messages.UnusedImport):
underline_import(error.lineno, error.message, underlines)
elif isinstance(error, pyflakes.messages.ImportStarUsed):
underline_import(error.lineno, '*', underlines)
elif isinstance(error, pyflakes.messages.DuplicateArgument):
underline_duplicate_argument(error.lineno, error.message, underlines)
elif isinstance(error, pyflakes.messages.LateFutureImport):
pass
else:
print 'Oops, we missed an error type!', type(error)