266 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			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)
 |