feat(SublimeText2.WebPackages): cache packages
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# Note: Unlike linter modules, changes made to this module will NOT take effect until
|
||||
# Sublime Text is restarted.
|
||||
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
import modules.base_linter as base_linter
|
||||
|
||||
# sys.path appears to ignore individual paths with unicode characters.
|
||||
# This means that this lib_path will be ignored for Windows 7 users with
|
||||
# non-ascii characters in their username (thus as their home directory).
|
||||
#
|
||||
# libs_path = os.path.abspath(os.path.join(os.path.dirname(__file__.encode('utf-8')), u'modules', u'libs'))
|
||||
#
|
||||
# if libs_path not in sys.path:
|
||||
# sys.path.insert(0, libs_path)
|
||||
|
||||
# As a fix for the Windows 7 lib path issue (#181), the individual modules in
|
||||
# the `libs` folder can be explicitly imported. This obviously doesn't scale
|
||||
# well, but may be a necessary evil until ST2 upgrades its internal Python.
|
||||
#
|
||||
tmpdir = os.getcwdu()
|
||||
os.chdir(os.path.abspath(os.path.join(os.path.dirname(__file__.encode('utf-8')), u'modules', u'libs')))
|
||||
|
||||
for mod in [u'capp_lint', u'pep8', u'pyflakes', u'pyflakes.checker', u'pyflakes.messages']:
|
||||
__import__(mod)
|
||||
print u'imported {0}'.format(mod)
|
||||
|
||||
os.chdir(tmpdir)
|
||||
|
||||
|
||||
class Loader(object):
|
||||
'''utility class to load (and reload if necessary) SublimeLinter modules'''
|
||||
def __init__(self, basedir, linters):
|
||||
'''assign relevant variables and load all existing linter modules'''
|
||||
self.basedir = basedir
|
||||
self.basepath = u'sublimelinter/modules'
|
||||
self.linters = linters
|
||||
self.modpath = self.basepath.replace('/', u'.')
|
||||
self.ignored = ('__init__', 'base_linter')
|
||||
self.fix_path()
|
||||
self.load_all()
|
||||
|
||||
def fix_path(self):
|
||||
if os.name != 'posix':
|
||||
return
|
||||
|
||||
path = os.environ['PATH'].encode('utf-8')
|
||||
|
||||
if path:
|
||||
dirs = path.encode('utf-8').split(':')
|
||||
|
||||
if u'/usr/local/bin' not in dirs:
|
||||
dirs.insert(0, u'/usr/local/bin')
|
||||
|
||||
if u'~/bin' not in dirs and u'$HOME/bin' not in dirs:
|
||||
dirs.append(u'$HOME/bin')
|
||||
|
||||
os.environ['PATH'] = u':'.join(dirs)
|
||||
|
||||
|
||||
def load_all(self):
|
||||
'''loads all existing linter modules'''
|
||||
for modf in glob.glob(u'{0}/*.py'.format(self.basepath)):
|
||||
base, name = os.path.split(modf)
|
||||
name = name.split('.', 1)[0]
|
||||
|
||||
if name in self.ignored:
|
||||
continue
|
||||
|
||||
self.load_module(name)
|
||||
|
||||
def load_module(self, name):
|
||||
'''loads a single linter module'''
|
||||
fullmod = u'{0}.{1}'.format(self.modpath, name)
|
||||
|
||||
# make sure the path didn't change on us (this is needed for submodule reload)
|
||||
pushd = os.getcwdu()
|
||||
os.chdir(self.basedir)
|
||||
|
||||
__import__(fullmod)
|
||||
|
||||
# this following line of code does two things:
|
||||
# first, we get the actual module from sys.modules,
|
||||
# not the base mod returned by __import__
|
||||
# second, we get an updated version with reload()
|
||||
# so module development is easier
|
||||
# (to make sublime text reload language submodule,
|
||||
# just save sublimelinter_plugin.py )
|
||||
mod = sys.modules[fullmod] = reload(sys.modules[fullmod])
|
||||
|
||||
# update module's __file__ to absolute path so we can reload it
|
||||
# if saved with sublime text
|
||||
mod.__file__ = os.path.abspath(mod.__file__.encode('utf-8')).rstrip('co')
|
||||
|
||||
language = ''
|
||||
|
||||
try:
|
||||
config = base_linter.CONFIG.copy()
|
||||
|
||||
try:
|
||||
config.update(mod.CONFIG)
|
||||
language = config['language']
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
|
||||
if language:
|
||||
if hasattr(mod, 'Linter'):
|
||||
linter = mod.Linter(config)
|
||||
else:
|
||||
linter = base_linter.BaseLinter(config)
|
||||
|
||||
lc_language = language.lower()
|
||||
self.linters[lc_language] = linter
|
||||
print u'SublimeLinter: {0} loaded'.format(language)
|
||||
else:
|
||||
print u'SublimeLinter: {0} disabled (no language specified in module)'.format(name)
|
||||
|
||||
except KeyError:
|
||||
print u'SublimeLinter: general error importing {0} ({1})'.format(name, language or '<unknown>')
|
||||
|
||||
os.chdir(pushd)
|
||||
|
||||
def reload_module(self, module):
|
||||
'''reload a single linter module
|
||||
This method is meant to be used when editing a given
|
||||
linter module so that changes can be viewed immediately
|
||||
upon saving without having to restart Sublime Text'''
|
||||
fullmod = module.__name__
|
||||
|
||||
if not fullmod.startswith(self.modpath):
|
||||
return
|
||||
|
||||
name = fullmod.replace(self.modpath + '.', '', 1)
|
||||
self.load_module(name)
|
@@ -0,0 +1,412 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# base_linter.py - base class for linters
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
import sublime
|
||||
|
||||
# If the linter uses an executable that takes stdin, use this input method.
|
||||
INPUT_METHOD_STDIN = 1
|
||||
|
||||
# If the linter uses an executable that does not take stdin but you wish to use
|
||||
# a temp file so that the current view can be linted interactively, use this input method.
|
||||
# If the current view has been saved, the tempfile will have the same name as the
|
||||
# view's file, which is necessary for some linters.
|
||||
INPUT_METHOD_TEMP_FILE = 2
|
||||
|
||||
# If the linter uses an executable that does not take stdin and you wish to have
|
||||
# linting occur only on file load and save, use this input method.
|
||||
INPUT_METHOD_FILE = 3
|
||||
|
||||
CONFIG = {
|
||||
# The display language name for this linter.
|
||||
'language': '',
|
||||
|
||||
# Linters may either use built in code or use an external executable. This item may have
|
||||
# one of the following values:
|
||||
#
|
||||
# string - An external command (or path to a command) to execute
|
||||
# None - The linter is considered to be built in
|
||||
#
|
||||
# Alternately, your linter class may define the method get_executable(),
|
||||
# which should return the three-tuple (<enabled>, <executable>, <message>):
|
||||
# <enabled> must be a boolean than indicates whether the executable is available and usable.
|
||||
# If <enabled> is True, <executable> must be one of:
|
||||
# - A command string (or path to a command) if an external executable will be used
|
||||
# - None if built in code will be used
|
||||
# - False if no suitable executable can be found or the linter should be disabled
|
||||
# for some other reason.
|
||||
# <message> is the message that will be shown in the console when the linter is
|
||||
# loaded, to aid the user in knowing what the status of the linter is. If None or an empty string,
|
||||
# a default message will be returned based on the value of <executable>. Otherwise it
|
||||
# must be a string.
|
||||
'executable': None,
|
||||
|
||||
# If an external executable is being used, this item specifies the arguments
|
||||
# used when checking the existence of the executable to determine if the linter can be enabled.
|
||||
# If more than one argument needs to be passed, use a tuple/list.
|
||||
# Defaults to '-v' if this item is missing.
|
||||
'test_existence_args': '-v',
|
||||
|
||||
# If an external executable is being used, this item specifies the arguments to be passed
|
||||
# when linting. If there is more than one argument, use a tuple/list.
|
||||
# If the input method is anything other than INPUT_METHOD_STDIN, put a {filename} placeholder in
|
||||
# the args where the filename should go.
|
||||
#
|
||||
# Alternately, if your linter class may define the method get_lint_args(), which should return
|
||||
# None for no arguments or a tuple/list for one or more arguments.
|
||||
'lint_args': None,
|
||||
|
||||
# If an external executable is being used, the method used to pass input to it. Defaults to STDIN.
|
||||
'input_method': INPUT_METHOD_STDIN
|
||||
}
|
||||
|
||||
TEMPFILES_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__.encode('utf-8')), u'..', u'.tempfiles'))
|
||||
|
||||
JSON_MULTILINE_COMMENT_RE = re.compile(r'\/\*[\s\S]*?\*\/')
|
||||
JSON_SINGLELINE_COMMENT_RE = re.compile(r'\/\/[^\n\r]*')
|
||||
|
||||
if not os.path.exists(TEMPFILES_DIR):
|
||||
os.mkdir(TEMPFILES_DIR)
|
||||
|
||||
|
||||
class BaseLinter(object):
|
||||
'''A base class for linters. Your linter module needs to do the following:
|
||||
|
||||
- Set the relevant values in CONFIG
|
||||
- Override built_in_check() if it uses a built in linter. You may return
|
||||
whatever value you want, this value will be passed to parse_errors().
|
||||
- Override parse_errors() and populate the relevant lists/dicts. The errors
|
||||
argument passed to parse_errors() is the output of the executable run through strip().
|
||||
|
||||
If you do subclass and override __init__, be sure to call super(MyLinter, self).__init__(config).
|
||||
'''
|
||||
|
||||
JSC_PATH = '/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc'
|
||||
|
||||
LIB_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__.encode('utf-8')), u'libs'))
|
||||
|
||||
JAVASCRIPT_ENGINES = ['node', 'jsc']
|
||||
JAVASCRIPT_ENGINE_NAMES = {'node': 'node.js', 'jsc': 'JavaScriptCore'}
|
||||
JAVASCRIPT_ENGINE_WRAPPERS_PATH = os.path.join(LIB_PATH, 'jsengines')
|
||||
|
||||
def __init__(self, config):
|
||||
self.language = config['language']
|
||||
self.enabled = False
|
||||
self.executable = config.get('executable', None)
|
||||
self.test_existence_args = config.get('test_existence_args', ['-v'])
|
||||
self.js_engine = None
|
||||
|
||||
if isinstance(self.test_existence_args, basestring):
|
||||
self.test_existence_args = (self.test_existence_args,)
|
||||
|
||||
self.input_method = config.get('input_method', INPUT_METHOD_STDIN)
|
||||
self.filename = None
|
||||
self.lint_args = config.get('lint_args', [])
|
||||
|
||||
if isinstance(self.lint_args, basestring):
|
||||
self.lint_args = [self.lint_args]
|
||||
|
||||
def check_enabled(self, view):
|
||||
if hasattr(self, 'get_executable'):
|
||||
try:
|
||||
self.enabled, self.executable, message = self.get_executable(view)
|
||||
|
||||
if self.enabled and not message:
|
||||
message = 'using "{0}"'.format(self.executable) if self.executable else 'built in'
|
||||
except Exception as ex:
|
||||
self.enabled = False
|
||||
message = unicode(ex)
|
||||
else:
|
||||
self.enabled, message = self._check_enabled(view)
|
||||
|
||||
return (self.enabled, message or '<unknown reason>')
|
||||
|
||||
def _check_enabled(self, view):
|
||||
if self.executable is None:
|
||||
return (True, 'built in')
|
||||
elif isinstance(self.executable, basestring):
|
||||
self.executable = self.get_mapped_executable(view, self.executable)
|
||||
elif isinstance(self.executable, bool) and self.executable == False:
|
||||
return (False, 'unknown error')
|
||||
else:
|
||||
return (False, 'bad type for CONFIG["executable"]')
|
||||
|
||||
# If we get this far, the executable is external. Test that it can be executed
|
||||
# and capture stdout and stderr so they don't end up in the system log.
|
||||
try:
|
||||
args = [self.executable]
|
||||
args.extend(self.test_existence_args)
|
||||
subprocess.Popen(args, startupinfo=self.get_startupinfo(),
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()
|
||||
except OSError:
|
||||
return (False, '"{0}" cannot be found'.format(self.executable))
|
||||
|
||||
return (True, 'using "{0}" for executable'.format(self.executable))
|
||||
|
||||
def _get_lint_args(self, view, code, filename):
|
||||
if hasattr(self, 'get_lint_args'):
|
||||
return self.get_lint_args(view, code, filename) or []
|
||||
else:
|
||||
lintArgs = self.lint_args or []
|
||||
settings = view.settings().get('SublimeLinter', {}).get(self.language, {})
|
||||
|
||||
if settings:
|
||||
args = settings.get('lint_args', [])
|
||||
lintArgs.extend(args)
|
||||
|
||||
cwd = settings.get('working_directory').encode('utf-8')
|
||||
|
||||
if cwd and os.path.isabs(cwd) and os.path.isdir(cwd):
|
||||
os.chdir(cwd)
|
||||
|
||||
return [arg.format(filename=filename) for arg in lintArgs]
|
||||
|
||||
def built_in_check(self, view, code, filename):
|
||||
return ''
|
||||
|
||||
def executable_check(self, view, code, filename):
|
||||
args = [self.executable]
|
||||
tempfilePath = None
|
||||
|
||||
if self.input_method == INPUT_METHOD_STDIN:
|
||||
args.extend(self._get_lint_args(view, code, filename))
|
||||
|
||||
elif self.input_method == INPUT_METHOD_TEMP_FILE:
|
||||
if filename:
|
||||
filename = os.path.basename(filename)
|
||||
else:
|
||||
filename = u'view{0}'.format(view.id())
|
||||
|
||||
tempfilePath = os.path.join(TEMPFILES_DIR, filename)
|
||||
|
||||
with open(tempfilePath, 'w') as f:
|
||||
f.write(code)
|
||||
|
||||
args.extend(self._get_lint_args(view, code, tempfilePath))
|
||||
code = u''
|
||||
|
||||
elif self.input_method == INPUT_METHOD_FILE:
|
||||
args.extend(self._get_lint_args(view, code, filename))
|
||||
code = u''
|
||||
|
||||
else:
|
||||
return u''
|
||||
|
||||
try:
|
||||
process = subprocess.Popen(args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=self.get_startupinfo())
|
||||
process.stdin.write(code)
|
||||
result = process.communicate()[0]
|
||||
finally:
|
||||
if tempfilePath:
|
||||
os.remove(tempfilePath)
|
||||
|
||||
return result.strip()
|
||||
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
pass
|
||||
|
||||
def add_message(self, lineno, lines, message, messages):
|
||||
# Assume lineno is one-based, ST2 wants zero-based line numbers
|
||||
lineno -= 1
|
||||
lines.add(lineno)
|
||||
message = message[0].upper() + message[1:]
|
||||
|
||||
# Remove trailing period from error message
|
||||
if message[-1] == '.':
|
||||
message = message[:-1]
|
||||
|
||||
if lineno in messages:
|
||||
messages[lineno].append(message)
|
||||
else:
|
||||
messages[lineno] = [message]
|
||||
|
||||
def underline_range(self, view, lineno, position, underlines, length=1):
|
||||
# Assume lineno is one-based, ST2 wants zero-based line numbers
|
||||
lineno -= 1
|
||||
line = view.full_line(view.text_point(lineno, 0))
|
||||
position += line.begin()
|
||||
|
||||
for i in xrange(length):
|
||||
underlines.append(sublime.Region(position + i))
|
||||
|
||||
def underline_regex(self, view, lineno, regex, lines, underlines, wordmatch=None, linematch=None):
|
||||
# Assume lineno is one-based, ST2 wants zero-based line numbers
|
||||
lineno -= 1
|
||||
lines.add(lineno)
|
||||
offset = 0
|
||||
line = view.full_line(view.text_point(lineno, 0))
|
||||
lineText = view.substr(line)
|
||||
|
||||
if linematch:
|
||||
match = re.match(linematch, lineText)
|
||||
|
||||
if match:
|
||||
lineText = match.group('match')
|
||||
offset = match.start('match')
|
||||
else:
|
||||
return
|
||||
|
||||
iters = re.finditer(regex, lineText)
|
||||
results = [(result.start('underline'), result.end('underline')) for result in iters
|
||||
if not wordmatch or result.group('underline') == wordmatch]
|
||||
|
||||
# Make the lineno one-based again for underline_range
|
||||
lineno += 1
|
||||
|
||||
for start, end in results:
|
||||
self.underline_range(view, lineno, start + offset, underlines, end - start)
|
||||
|
||||
def underline_word(self, view, lineno, position, underlines):
|
||||
# Assume lineno is one-based, ST2 wants zero-based line numbers
|
||||
lineno -= 1
|
||||
line = view.full_line(view.text_point(lineno, 0))
|
||||
position += line.begin()
|
||||
|
||||
word = view.word(position)
|
||||
underlines.append(word)
|
||||
|
||||
def run(self, view, code, filename=None):
|
||||
self.filename = filename
|
||||
|
||||
if self.executable is None:
|
||||
errors = self.built_in_check(view, code, filename)
|
||||
else:
|
||||
errors = self.executable_check(view, code, filename)
|
||||
|
||||
lines = set()
|
||||
errorUnderlines = [] # leave this here for compatibility with original plugin
|
||||
errorMessages = {}
|
||||
violationUnderlines = []
|
||||
violationMessages = {}
|
||||
warningUnderlines = []
|
||||
warningMessages = {}
|
||||
|
||||
self.parse_errors(view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages)
|
||||
return lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages
|
||||
|
||||
def get_mapped_executable(self, view, default):
|
||||
map = view.settings().get('sublimelinter_executable_map')
|
||||
|
||||
if map:
|
||||
lang = self.language.lower()
|
||||
|
||||
if lang in map:
|
||||
return map[lang].encode('utf-8')
|
||||
|
||||
return default
|
||||
|
||||
def get_startupinfo(self):
|
||||
info = None
|
||||
|
||||
if os.name == 'nt':
|
||||
info = subprocess.STARTUPINFO()
|
||||
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
info.wShowWindow = subprocess.SW_HIDE
|
||||
|
||||
return info
|
||||
|
||||
def execute_get_output(self, args):
|
||||
try:
|
||||
return subprocess.Popen(args, self.get_startupinfo()).communicate()[0]
|
||||
except:
|
||||
return ''
|
||||
|
||||
def jsc_path(self):
|
||||
'''Return the path to JavaScriptCore. Use this method in case the path
|
||||
has to be dynamically calculated in the future.'''
|
||||
return self.JSC_PATH
|
||||
|
||||
def find_file(self, filename, view):
|
||||
'''Find a file with the given name, starting in the view's directory,
|
||||
then ascending the file hierarchy up to root.'''
|
||||
path = view.file_name().encode('utf-8')
|
||||
|
||||
# quit if the view is temporary
|
||||
if not path:
|
||||
return None
|
||||
|
||||
dirname = os.path.dirname(path)
|
||||
|
||||
while True:
|
||||
path = os.path.join(dirname, filename)
|
||||
|
||||
if os.path.isfile(path):
|
||||
with open(path, 'r') as f:
|
||||
return f.read()
|
||||
|
||||
# if we hit root, quit
|
||||
parent = os.path.dirname(dirname)
|
||||
|
||||
if parent == dirname:
|
||||
return None
|
||||
else:
|
||||
dirname = parent
|
||||
|
||||
def strip_json_comments(self, json_str):
|
||||
stripped_json = JSON_MULTILINE_COMMENT_RE.sub('', json_str)
|
||||
stripped_json = JSON_SINGLELINE_COMMENT_RE.sub('', stripped_json)
|
||||
return json.dumps(json.loads(stripped_json))
|
||||
|
||||
def get_javascript_args(self, view, linter, code):
|
||||
path = os.path.join(self.LIB_PATH, linter)
|
||||
options = self.get_javascript_options(view)
|
||||
|
||||
if options == None:
|
||||
options = json.dumps(view.settings().get('%s_options' % linter) or {})
|
||||
|
||||
self.get_javascript_engine(view)
|
||||
engine = self.js_engine
|
||||
|
||||
if (engine['name'] == 'jsc'):
|
||||
args = [engine['wrapper'], '--', path + os.path.sep, str(code.count('\n')), options]
|
||||
else:
|
||||
args = [engine['wrapper'], path + os.path.sep, options]
|
||||
|
||||
return args
|
||||
|
||||
def get_javascript_options(self, view):
|
||||
'''Subclasses should override this if they want to provide options
|
||||
for a Javascript-based linter. If the subclass cannot provide
|
||||
options, it should return None (or not return anything).'''
|
||||
return None
|
||||
|
||||
def get_javascript_engine(self, view):
|
||||
if self.js_engine == None:
|
||||
for engine in self.JAVASCRIPT_ENGINES:
|
||||
if engine == 'node':
|
||||
try:
|
||||
path = self.get_mapped_executable(view, 'node')
|
||||
subprocess.call([path, u'-v'], startupinfo=self.get_startupinfo())
|
||||
self.js_engine = {
|
||||
'name': engine,
|
||||
'path': path,
|
||||
'wrapper': os.path.join(self.JAVASCRIPT_ENGINE_WRAPPERS_PATH, engine + '.js'),
|
||||
}
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
elif engine == 'jsc':
|
||||
if os.path.exists(self.jsc_path()):
|
||||
self.js_engine = {
|
||||
'name': engine,
|
||||
'path': self.jsc_path(),
|
||||
'wrapper': os.path.join(self.JAVASCRIPT_ENGINE_WRAPPERS_PATH, engine + '.js'),
|
||||
}
|
||||
break
|
||||
|
||||
if self.js_engine != None:
|
||||
return (True, self.js_engine['path'], 'using {0}'.format(self.JAVASCRIPT_ENGINE_NAMES[self.js_engine['name']]))
|
||||
|
||||
# Didn't find an engine, tell the user
|
||||
engine_list = ', '.join(self.JAVASCRIPT_ENGINE_NAMES.values())
|
||||
return (False, '', 'One of the following Javascript engines must be installed: ' + engine_list)
|
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# cpp.py - sublimelint package for checking C++ files
|
||||
|
||||
import re
|
||||
|
||||
from base_linter import BaseLinter, INPUT_METHOD_TEMP_FILE
|
||||
|
||||
CONFIG = {
|
||||
'language': 'C',
|
||||
'executable': 'cppcheck',
|
||||
'lint_args': ['--enable=style', '--quiet', '{filename}'],
|
||||
'input_method': INPUT_METHOD_TEMP_FILE
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
CPPCHECK_RE = re.compile(r'\[.+?:(\d+?)\](.+)')
|
||||
|
||||
def __init__(self, config):
|
||||
super(Linter, self).__init__(config)
|
||||
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines,
|
||||
violationUnderlines, warningUnderlines,
|
||||
errorMessages, violationMessages,
|
||||
warningMessages):
|
||||
# Go through each line in the output of cppcheck
|
||||
for line in errors.splitlines():
|
||||
match = self.CPPCHECK_RE.match(line)
|
||||
if match:
|
||||
# The regular expression matches the line number and
|
||||
# the message as its two groups.
|
||||
lineno, message = match.group(1), match.group(2)
|
||||
# Remove the colon at the beginning of the message
|
||||
if len(message) > 0 and message[0] == ':':
|
||||
message = message[1:].strip()
|
||||
lineno = int(lineno)
|
||||
self.add_message(lineno, lines, message, errorMessages)
|
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# cpp.py - sublimelint package for checking C++ files (based on ruby.py)
|
||||
|
||||
import re
|
||||
|
||||
from base_linter import BaseLinter, INPUT_METHOD_TEMP_FILE
|
||||
|
||||
CONFIG = {
|
||||
'language': 'c_cpplint',
|
||||
'executable': 'cpplint.py',
|
||||
'test_existence_args': ['--help'],
|
||||
'lint_args': '{filename}',
|
||||
'input_method': INPUT_METHOD_TEMP_FILE
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
for line in errors.splitlines():
|
||||
match = re.match(r'^.+:(?P<line>\d+):\s+(?P<error>.+)', line)
|
||||
|
||||
if match:
|
||||
error, line = match.group('error'), match.group('line')
|
||||
self.add_message(int(line), lines, error, errorMessages)
|
@@ -0,0 +1,145 @@
|
||||
import re
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
import simplejson
|
||||
except ImportError:
|
||||
import json as simplejson
|
||||
|
||||
from base_linter import BaseLinter, TEMPFILES_DIR
|
||||
|
||||
CONFIG = {'language': 'CoffeeScript'}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
coffeelint_config = {}
|
||||
|
||||
def _test_executable(self, executable):
|
||||
try:
|
||||
args = [executable]
|
||||
args.extend(self.test_existence_args)
|
||||
subprocess.Popen(
|
||||
args,
|
||||
startupinfo=self.get_startupinfo(),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT
|
||||
).communicate()
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def get_executable(self, view):
|
||||
if os.name == 'nt':
|
||||
lint_command, coffee_command = (
|
||||
'coffeelint.cmd',
|
||||
'coffee.cmd',
|
||||
)
|
||||
else:
|
||||
lint_command, coffee_command = (
|
||||
'coffeelint',
|
||||
'coffee',
|
||||
)
|
||||
|
||||
executable = None
|
||||
|
||||
if self._test_executable(lint_command):
|
||||
executable = lint_command
|
||||
self.mode = 'coffeelint'
|
||||
elif self._test_executable(coffee_command):
|
||||
self.mode = 'coffee'
|
||||
executable = coffee_command
|
||||
|
||||
enabled = executable != None
|
||||
|
||||
return (
|
||||
enabled,
|
||||
executable,
|
||||
'using "%s" for executable' % executable if enabled else 'neither coffeelint nor coffee are available'
|
||||
)
|
||||
|
||||
def get_lint_args(self, view, code, filename):
|
||||
if self.mode == 'coffeelint':
|
||||
args = [
|
||||
'--stdin',
|
||||
'--nocolor',
|
||||
'--csv',
|
||||
]
|
||||
|
||||
new_config = view.settings().get('coffeelint_options', {})
|
||||
|
||||
if new_config != {}:
|
||||
# config based on tab/space and indentation settings set in
|
||||
# ST2 if these values aren't already set
|
||||
new_config['no_tabs']['level'] = new_config['no_tabs'].get(
|
||||
'level',
|
||||
'error' if view.settings().get('translate_tabs_to_spaces', False) else 'ignore'
|
||||
)
|
||||
new_config['indentation']['value'] = new_config['indentation'].get(
|
||||
'value',
|
||||
view.settings().get('tab_size', 8) if view.settings().get('translate_tabs_to_spaces', False) else 1
|
||||
)
|
||||
|
||||
if new_config != self.coffeelint_config:
|
||||
self.coffeelint_config = new_config
|
||||
self.write_config_file()
|
||||
|
||||
args += ['--file', self.coffeelint_config_file]
|
||||
|
||||
return args
|
||||
else:
|
||||
return ('-s', '-l')
|
||||
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines,
|
||||
violationUnderlines, warningUnderlines, errorMessages,
|
||||
violationMessages, warningMessages):
|
||||
|
||||
for line in errors.splitlines():
|
||||
match = re.match(
|
||||
r'(?:stdin,\d+,error,)?Error: Parse error on line (?P<line>\d+): (?P<error>.+)',
|
||||
line
|
||||
)
|
||||
if match == None:
|
||||
match = re.match(
|
||||
r'stdin,(?P<line>\d+),(?P<type>[a-z]+),(?P<error>.*)',
|
||||
line
|
||||
)
|
||||
if match == None:
|
||||
match = re.match(
|
||||
r'Error: (?P<error>.+) on line (?P<line>\d+)',
|
||||
line
|
||||
)
|
||||
|
||||
if match:
|
||||
line_num, error_text = (
|
||||
int(match.group('line')),
|
||||
match.group('error'),
|
||||
)
|
||||
|
||||
try:
|
||||
error_type = match.group('type')
|
||||
except IndexError:
|
||||
error_type = None
|
||||
|
||||
self.add_message(
|
||||
line_num,
|
||||
lines,
|
||||
error_text,
|
||||
errorMessages if error_type == 'error' else warningMessages
|
||||
)
|
||||
|
||||
def write_config_file(self):
|
||||
"""
|
||||
coffeelint requires a config file to be able to pass configuration
|
||||
variables to the program. this function writes a configuration file to
|
||||
hold them and sets the location of the file
|
||||
"""
|
||||
self.coffeelint_config_file = os.path.join(TEMPFILES_DIR, 'coffeelint.json')
|
||||
temp_file = open(self.coffeelint_config_file, 'w')
|
||||
temp_file.write(
|
||||
simplejson.dumps(
|
||||
self.coffeelint_config,
|
||||
separators=(',', ':')
|
||||
)
|
||||
)
|
||||
temp_file.close()
|
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# css.py - sublimelint package for checking CSS files
|
||||
|
||||
import json
|
||||
|
||||
from base_linter import BaseLinter
|
||||
|
||||
CONFIG = {
|
||||
'language': 'CSS'
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
def __init__(self, config):
|
||||
super(Linter, self).__init__(config)
|
||||
|
||||
def get_executable(self, view):
|
||||
return self.get_javascript_engine(view)
|
||||
|
||||
def get_lint_args(self, view, code, filename):
|
||||
return self.get_javascript_args(view, 'csslint', code)
|
||||
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
try:
|
||||
errors = json.loads(errors.strip() or '[]')
|
||||
except ValueError:
|
||||
raise ValueError("Error from csslint: {0}".format(errors))
|
||||
|
||||
for error in errors:
|
||||
lineno = error['line']
|
||||
|
||||
if error['type'] == 'warning':
|
||||
messages = warningMessages
|
||||
underlines = warningUnderlines
|
||||
else:
|
||||
messages = errorMessages
|
||||
underlines = errorUnderlines
|
||||
|
||||
self.add_message(lineno, lines, error['reason'], messages)
|
||||
self.underline_range(view, lineno, error['character'] - 1, underlines)
|
@@ -0,0 +1,111 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# git_commit_message.py - sublimelint package for checking Git commit messages
|
||||
|
||||
from base_linter import BaseLinter
|
||||
|
||||
|
||||
CONFIG = {
|
||||
'language': 'Git Commit Message'
|
||||
}
|
||||
|
||||
|
||||
class ErrorType:
|
||||
WARNING = 'warning'
|
||||
VIOLATION = 'violation'
|
||||
ERROR = 'error'
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
|
||||
def built_in_check(self, view, code, filename):
|
||||
lines = code.splitlines()
|
||||
lineno = 0
|
||||
real_lineno = 0
|
||||
first_line_of_message = None
|
||||
first_line_of_body = None
|
||||
errors = []
|
||||
|
||||
for line in lines:
|
||||
real_lineno += 1
|
||||
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
|
||||
if line.startswith('diff --git'):
|
||||
break
|
||||
|
||||
lineno += 1
|
||||
|
||||
if first_line_of_message is None:
|
||||
if line.strip():
|
||||
first_line_of_message = lineno
|
||||
|
||||
if len(line) > 68:
|
||||
errors.append({
|
||||
'type': ErrorType.ERROR,
|
||||
'message': 'Subject line must be 68 characters or less (github will truncate).',
|
||||
'lineno': real_lineno,
|
||||
'col': 68,
|
||||
})
|
||||
elif len(line) > 50:
|
||||
errors.append({
|
||||
'type': ErrorType.WARNING,
|
||||
'message': 'Subject line should be 50 characters or less.',
|
||||
'lineno': real_lineno,
|
||||
'col': 50,
|
||||
})
|
||||
elif lineno != 1:
|
||||
errors.append({
|
||||
'type': ErrorType.ERROR,
|
||||
'message': 'Subject must be on first line.',
|
||||
'lineno': real_lineno,
|
||||
})
|
||||
elif line[0].upper() != line[0]:
|
||||
errors.append({
|
||||
'type': ErrorType.VIOLATION,
|
||||
'message': 'Subject line should be capitalized.',
|
||||
'lineno': real_lineno,
|
||||
})
|
||||
elif first_line_of_body is None:
|
||||
if len(line):
|
||||
first_line_of_body = lineno
|
||||
|
||||
if lineno == first_line_of_message + 1:
|
||||
if len(line):
|
||||
errors.append({
|
||||
'message': 'Leave a blank line between the message subject and body.',
|
||||
'lineno': first_line_of_message + 1,
|
||||
})
|
||||
elif lineno > first_line_of_message + 2:
|
||||
errors.append({
|
||||
'message': 'Leave exactly 1 blank line between the message subject and body.',
|
||||
'lineno': real_lineno,
|
||||
})
|
||||
if first_line_of_body is not None:
|
||||
if len(line) > 72:
|
||||
errors.append({
|
||||
'message': 'Lines must not exceed 72 characters.',
|
||||
'lineno': real_lineno,
|
||||
'col': 72,
|
||||
})
|
||||
|
||||
return errors
|
||||
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
for error in errors:
|
||||
error_type = error.get('type', ErrorType.ERROR)
|
||||
col = error.get('col', 0)
|
||||
|
||||
messages = {
|
||||
ErrorType.WARNING: warningMessages,
|
||||
ErrorType.VIOLATION: violationMessages,
|
||||
ErrorType.ERROR: errorMessages,
|
||||
}[error_type]
|
||||
underlines = {
|
||||
ErrorType.WARNING: warningUnderlines,
|
||||
ErrorType.VIOLATION: violationUnderlines,
|
||||
ErrorType.ERROR: errorUnderlines,
|
||||
}[error_type]
|
||||
|
||||
self.add_message(error['lineno'], lines, error['message'], messages)
|
||||
self.underline_range(view, error['lineno'], col, underlines, length=1)
|
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# haml.py - sublimelint package for checking haml files
|
||||
|
||||
import re
|
||||
|
||||
from base_linter import BaseLinter
|
||||
|
||||
CONFIG = {
|
||||
'language': 'Ruby Haml',
|
||||
'executable': 'haml',
|
||||
'lint_args': '-c'
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
for line in errors.splitlines():
|
||||
match = re.match(r'^.+(?P<line>\d+):\s+(?P<error>.+)', line)
|
||||
|
||||
if match:
|
||||
error, line = match.group('error'), match.group('line')
|
||||
self.add_message(int(line), lines, error, errorMessages)
|
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# html.py - sublimelint package for checking html files
|
||||
|
||||
# Example error messages
|
||||
#
|
||||
# line 1 column 1 - Warning: missing <!DOCTYPE> declaration
|
||||
# line 200 column 1 - Warning: discarding unexpected </div>
|
||||
# line 1 column 1 - Warning: inserting missing 'title' element
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from base_linter import BaseLinter
|
||||
|
||||
CONFIG = {
|
||||
'language': 'HTML',
|
||||
'executable': 'tidy',
|
||||
'lint_args': '-eq'
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
def get_executable(self, view):
|
||||
try:
|
||||
path = self.get_mapped_executable(view, 'tidy')
|
||||
version_string = subprocess.Popen([path, '-v'], startupinfo=self.get_startupinfo(), stdout=subprocess.PIPE).communicate()[0]
|
||||
|
||||
if u'HTML5' in version_string:
|
||||
return (True, path, 'using tidy for executable')
|
||||
|
||||
return (False, '', 'tidy is not ready for HTML5')
|
||||
except OSError:
|
||||
return (False, '', 'tidy cannot be found')
|
||||
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
for line in errors.splitlines():
|
||||
match = re.match(r'^line\s(?P<line>\d+)\scolumn\s\d+\s-\s(?P<error>.+)', line)
|
||||
|
||||
if match:
|
||||
error, line = match.group('error'), match.group('line')
|
||||
self.add_message(int(line), lines, error, errorMessages)
|
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# java.py - sublimelint package for checking java files
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
|
||||
from base_linter import BaseLinter, INPUT_METHOD_FILE
|
||||
|
||||
CONFIG = {
|
||||
'language': 'Java',
|
||||
'executable': 'javac',
|
||||
'test_existence_args': '-version',
|
||||
'input_method': INPUT_METHOD_FILE
|
||||
}
|
||||
|
||||
ERROR_RE = re.compile(r'^(?P<path>.*\.java):(?P<line>\d+): (?P<warning>warning: )?(?:\[\w+\] )?(?P<error>.*)')
|
||||
MARK_RE = re.compile(r'^(?P<mark>\s*)\^$')
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines,
|
||||
violationUnderlines, warningUnderlines, errorMessages,
|
||||
violationMessages, warningMessages):
|
||||
it = iter(errors.splitlines())
|
||||
|
||||
for line in it:
|
||||
match = re.match(ERROR_RE, line)
|
||||
|
||||
if match:
|
||||
path = os.path.abspath(match.group('path'))
|
||||
|
||||
if path != self.filename:
|
||||
continue
|
||||
|
||||
lineNumber = int(match.group('line'))
|
||||
warning = match.group('warning')
|
||||
error = match.group('error')
|
||||
|
||||
if warning:
|
||||
messages = warningMessages
|
||||
underlines = warningUnderlines
|
||||
else:
|
||||
messages = errorMessages
|
||||
underlines = errorUnderlines
|
||||
|
||||
# Skip forward until we find the marker
|
||||
position = -1
|
||||
|
||||
while True:
|
||||
line = it.next()
|
||||
match = re.match(MARK_RE, line)
|
||||
|
||||
if match:
|
||||
position = len(match.group('mark'))
|
||||
break
|
||||
|
||||
self.add_message(lineNumber, lines, error, messages)
|
||||
self.underline_range(view, lineNumber, position, underlines)
|
@@ -0,0 +1,80 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# javascript.py - sublimelint package for checking Javascript files
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from base_linter import BaseLinter, INPUT_METHOD_TEMP_FILE
|
||||
|
||||
CONFIG = {
|
||||
'language': 'JavaScript'
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
GJSLINT_RE = re.compile(r'Line (?P<line>\d+),\s*E:(?P<errnum>\d+):\s*(?P<message>.+)')
|
||||
|
||||
def __init__(self, config):
|
||||
super(Linter, self).__init__(config)
|
||||
self.linter = None
|
||||
|
||||
def get_executable(self, view):
|
||||
self.linter = view.settings().get('javascript_linter', 'jshint')
|
||||
|
||||
if (self.linter in ('jshint', 'jslint')):
|
||||
return self.get_javascript_engine(view)
|
||||
elif (self.linter == 'gjslint'):
|
||||
try:
|
||||
path = self.get_mapped_executable(view, 'gjslint')
|
||||
subprocess.call([path, u'--help'], startupinfo=self.get_startupinfo())
|
||||
self.input_method = INPUT_METHOD_TEMP_FILE
|
||||
return (True, path, 'using gjslint')
|
||||
except OSError:
|
||||
return (False, '', 'gjslint cannot be found')
|
||||
else:
|
||||
return (False, '', '"{0}" is not a valid javascript linter'.format(self.linter))
|
||||
|
||||
def get_lint_args(self, view, code, filename):
|
||||
if (self.linter == 'gjslint'):
|
||||
args = []
|
||||
gjslint_options = view.settings().get("gjslint_options", [])
|
||||
args.extend(gjslint_options)
|
||||
args.extend([u'--nobeep', filename])
|
||||
return args
|
||||
elif (self.linter in ('jshint', 'jslint')):
|
||||
return self.get_javascript_args(view, self.linter, code)
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_javascript_options(self, view):
|
||||
if self.linter == 'jshint':
|
||||
rc_options = self.find_file('.jshintrc', view)
|
||||
|
||||
if rc_options != None:
|
||||
rc_options = self.strip_json_comments(rc_options)
|
||||
return json.dumps(json.loads(rc_options))
|
||||
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
if (self.linter == 'gjslint'):
|
||||
ignore = view.settings().get('gjslint_ignore', [])
|
||||
|
||||
for line in errors.splitlines():
|
||||
match = self.GJSLINT_RE.match(line)
|
||||
|
||||
if match:
|
||||
line, errnum, message = match.group('line'), match.group('errnum'), match.group('message')
|
||||
|
||||
if (int(errnum) not in ignore):
|
||||
self.add_message(int(line), lines, message, errorMessages)
|
||||
|
||||
elif (self.linter in ('jshint', 'jslint')):
|
||||
try:
|
||||
errors = json.loads(errors.strip() or '[]')
|
||||
except ValueError:
|
||||
raise ValueError("Error from {0}: {1}".format(self.linter, errors))
|
||||
|
||||
for error in errors:
|
||||
lineno = error['line']
|
||||
self.add_message(lineno, lines, error['reason'], errorMessages)
|
||||
self.underline_range(view, lineno, error['character'] - 1, errorUnderlines)
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
/*jslint node: true, sloppy: true */
|
||||
/*globals LINTER_PATH, load */
|
||||
|
||||
var CSSLint = require("./csslint-node").CSSLint;
|
||||
|
||||
exports.lint = function (code, config) {
|
||||
var results = [];
|
||||
|
||||
var ruleset = {};
|
||||
|
||||
// rules that are `false` will be ignored.
|
||||
for (var ruleName in config) {
|
||||
|
||||
if (config[ruleName] === 'warning') {
|
||||
ruleset[ruleName] = 1;
|
||||
// Rules set to `true` or 'error' will be considered errors
|
||||
} else if (config[ruleName]) {
|
||||
ruleset[ruleName] = 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var report = CSSLint.verify(code, ruleset);
|
||||
|
||||
report.messages.forEach(function (message) {
|
||||
if (message) {
|
||||
|
||||
// message.type // warning|error
|
||||
// message.line
|
||||
// message.col
|
||||
// message.message
|
||||
// message.evidence // Requires sanitizing as it can include CR, LF
|
||||
// message.rule // The rule object
|
||||
|
||||
// We don't pass on the rollup messages
|
||||
if (message.rollup !== true) {
|
||||
results.push({
|
||||
'line': message.line,
|
||||
'character': message.col,
|
||||
'type': message.type,
|
||||
'reason': message.message
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
@@ -0,0 +1,51 @@
|
||||
/*jshint boss: true, evil: true */
|
||||
/*globals load quit readline lint JSHINT */
|
||||
|
||||
// usage:
|
||||
// jsc ${envHome}/jsc.js -- /path/to/linter/ ${lineCount} {option1:true,option2:false}
|
||||
|
||||
var USING_JSC = true,
|
||||
LINTER_PATH = arguments[0].replace(/\/$/, '') + '/';
|
||||
|
||||
var require = function (file) {
|
||||
load(LINTER_PATH + file.replace(/\.js$/, '') + '.js');
|
||||
return this;
|
||||
},
|
||||
exports = {};
|
||||
|
||||
require('linter');
|
||||
|
||||
if (typeof exports.lint === 'undefined') {
|
||||
print('JSC: Could not load linter.js.');
|
||||
quit();
|
||||
}
|
||||
|
||||
var process = function (args) {
|
||||
var opts,
|
||||
lineCount = parseInt(args[1], 10);
|
||||
|
||||
if (isNaN(lineCount)) {
|
||||
print('JSC: Must provide number of lines to read from stdin.');
|
||||
quit();
|
||||
}
|
||||
|
||||
try {
|
||||
opts = JSON.parse(args[2]);
|
||||
} catch (e) {
|
||||
} finally {
|
||||
if (!opts) opts = {};
|
||||
}
|
||||
|
||||
var code = readline();
|
||||
|
||||
for (var i = 0; i < lineCount; ++i) {
|
||||
code += '\n' + readline();
|
||||
}
|
||||
|
||||
var results = exports.lint(code, opts);
|
||||
|
||||
print(JSON.stringify(results));
|
||||
quit();
|
||||
};
|
||||
|
||||
process(arguments);
|
@@ -0,0 +1,37 @@
|
||||
/*jshint node:true */
|
||||
|
||||
/*
|
||||
usage: node /path/to/node.js /path/to/linter/ ["{option1:true,option2:false}"]
|
||||
*/
|
||||
|
||||
var _fs = require('fs'),
|
||||
_util = require('util'),
|
||||
_path = require('path'),
|
||||
linterPath = process.argv[2].replace(/\/$/, '') + '/',
|
||||
_linter = require(linterPath + 'linter');
|
||||
|
||||
function run() {
|
||||
var code = '',
|
||||
results,
|
||||
config = JSON.parse(process.argv[3] || '{}'),
|
||||
filename = process.argv[4] || '';
|
||||
|
||||
if (filename) {
|
||||
results = _linter.lint(_fs.readFileSync(filename, 'utf-8'), config, linterPath);
|
||||
_util.puts(JSON.stringify(results));
|
||||
} else {
|
||||
process.stdin.resume();
|
||||
process.stdin.setEncoding('utf8');
|
||||
|
||||
process.stdin.on('data', function (chunk) {
|
||||
code += chunk;
|
||||
});
|
||||
|
||||
process.stdin.on('end', function () {
|
||||
results = _linter.lint(code, config, linterPath);
|
||||
_util.puts(JSON.stringify(results));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
/*jshint node: true */
|
||||
/*globals LINTER_PATH load */
|
||||
|
||||
var JSHINT = require("./jshint").JSHINT;
|
||||
|
||||
exports.lint = function (code, config) {
|
||||
var globals,
|
||||
results = [];
|
||||
|
||||
if (config.globals) {
|
||||
globals = config.globals;
|
||||
delete config.globals;
|
||||
}
|
||||
|
||||
try {
|
||||
JSHINT(code, config, globals);
|
||||
} catch (e) {
|
||||
results.push({line: 1, character: 1, reason: e.message});
|
||||
} finally {
|
||||
JSHINT.errors.forEach(function (error) {
|
||||
if (error) {
|
||||
results.push(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
/*jslint node: true, sloppy: true */
|
||||
/*globals LINTER_PATH, load */
|
||||
|
||||
var JSLINT = require("./jslint").JSLINT;
|
||||
|
||||
exports.lint = function (code, config) {
|
||||
var results = [];
|
||||
|
||||
try {
|
||||
JSLINT(code, config);
|
||||
} catch (e) {
|
||||
results.push({line: 1, character: 1, reason: e.message});
|
||||
} finally {
|
||||
JSLINT.errors.forEach(function (error) {
|
||||
if (error) {
|
||||
results.push(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
|
||||
__version__ = '0.5.0'
|
@@ -0,0 +1,624 @@
|
||||
# -*- test-case-name: pyflakes -*-
|
||||
# (c) 2005-2010 Divmod, Inc.
|
||||
# See LICENSE file for details
|
||||
|
||||
import __builtin__
|
||||
import os.path
|
||||
import _ast
|
||||
|
||||
from pyflakes import messages
|
||||
|
||||
|
||||
# utility function to iterate over an AST node's children, adapted
|
||||
# from Python 2.6's standard ast module
|
||||
try:
|
||||
import ast
|
||||
iter_child_nodes = ast.iter_child_nodes
|
||||
except (ImportError, AttributeError):
|
||||
def iter_child_nodes(node, astcls=_ast.AST):
|
||||
"""
|
||||
Yield all direct child nodes of *node*, that is, all fields that are nodes
|
||||
and all items of fields that are lists of nodes.
|
||||
"""
|
||||
for name in node._fields:
|
||||
field = getattr(node, name, None)
|
||||
if isinstance(field, astcls):
|
||||
yield field
|
||||
elif isinstance(field, list):
|
||||
for item in field:
|
||||
yield item
|
||||
|
||||
|
||||
class Binding(object):
|
||||
"""
|
||||
Represents the binding of a value to a name.
|
||||
|
||||
The checker uses this to keep track of which names have been bound and
|
||||
which names have not. See L{Assignment} for a special type of binding that
|
||||
is checked with stricter rules.
|
||||
|
||||
@ivar used: pair of (L{Scope}, line-number) indicating the scope and
|
||||
line number that this binding was last used
|
||||
"""
|
||||
|
||||
def __init__(self, name, source):
|
||||
self.name = name
|
||||
self.source = source
|
||||
self.used = False
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
|
||||
self.name,
|
||||
self.source.lineno,
|
||||
id(self))
|
||||
|
||||
|
||||
|
||||
class UnBinding(Binding):
|
||||
'''Created by the 'del' operator.'''
|
||||
|
||||
|
||||
|
||||
class Importation(Binding):
|
||||
"""
|
||||
A binding created by an import statement.
|
||||
|
||||
@ivar fullName: The complete name given to the import statement,
|
||||
possibly including multiple dotted components.
|
||||
@type fullName: C{str}
|
||||
"""
|
||||
def __init__(self, name, source):
|
||||
self.fullName = name
|
||||
name = name.split('.')[0]
|
||||
super(Importation, self).__init__(name, source)
|
||||
|
||||
|
||||
|
||||
class Argument(Binding):
|
||||
"""
|
||||
Represents binding a name as an argument.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class Assignment(Binding):
|
||||
"""
|
||||
Represents binding a name with an explicit assignment.
|
||||
|
||||
The checker will raise warnings for any Assignment that isn't used. Also,
|
||||
the checker does not consider assignments in tuple/list unpacking to be
|
||||
Assignments, rather it treats them as simple Bindings.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class FunctionDefinition(Binding):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ExportBinding(Binding):
|
||||
"""
|
||||
A binding created by an C{__all__} assignment. If the names in the list
|
||||
can be determined statically, they will be treated as names for export and
|
||||
additional checking applied to them.
|
||||
|
||||
The only C{__all__} assignment that can be recognized is one which takes
|
||||
the value of a literal list containing literal strings. For example::
|
||||
|
||||
__all__ = ["foo", "bar"]
|
||||
|
||||
Names which are imported and not otherwise used but appear in the value of
|
||||
C{__all__} will not have an unused import warning reported for them.
|
||||
"""
|
||||
def names(self):
|
||||
"""
|
||||
Return a list of the names referenced by this binding.
|
||||
"""
|
||||
names = []
|
||||
if isinstance(self.source, _ast.List):
|
||||
for node in self.source.elts:
|
||||
if isinstance(node, _ast.Str):
|
||||
names.append(node.s)
|
||||
return names
|
||||
|
||||
|
||||
|
||||
class Scope(dict):
|
||||
importStarred = False # set to True when import * is found
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
|
||||
|
||||
|
||||
def __init__(self):
|
||||
super(Scope, self).__init__()
|
||||
|
||||
|
||||
|
||||
class ClassScope(Scope):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class FunctionScope(Scope):
|
||||
"""
|
||||
I represent a name scope for a function.
|
||||
|
||||
@ivar globals: Names declared 'global' in this function.
|
||||
"""
|
||||
def __init__(self):
|
||||
super(FunctionScope, self).__init__()
|
||||
self.globals = {}
|
||||
|
||||
|
||||
|
||||
class ModuleScope(Scope):
|
||||
pass
|
||||
|
||||
|
||||
# Globally defined names which are not attributes of the __builtin__ module.
|
||||
_MAGIC_GLOBALS = ['__file__', '__builtins__']
|
||||
|
||||
|
||||
|
||||
class Checker(object):
|
||||
"""
|
||||
I check the cleanliness and sanity of Python code.
|
||||
|
||||
@ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements
|
||||
of the list are two-tuples. The first element is the callable passed
|
||||
to L{deferFunction}. The second element is a copy of the scope stack
|
||||
at the time L{deferFunction} was called.
|
||||
|
||||
@ivar _deferredAssignments: Similar to C{_deferredFunctions}, but for
|
||||
callables which are deferred assignment checks.
|
||||
"""
|
||||
|
||||
nodeDepth = 0
|
||||
traceTree = False
|
||||
|
||||
def __init__(self, tree, filename='(none)'):
|
||||
self._deferredFunctions = []
|
||||
self._deferredAssignments = []
|
||||
self.dead_scopes = []
|
||||
self.messages = []
|
||||
self.filename = filename
|
||||
self.scopeStack = [ModuleScope()]
|
||||
self.futuresAllowed = True
|
||||
self.handleChildren(tree)
|
||||
self._runDeferred(self._deferredFunctions)
|
||||
# Set _deferredFunctions to None so that deferFunction will fail
|
||||
# noisily if called after we've run through the deferred functions.
|
||||
self._deferredFunctions = None
|
||||
self._runDeferred(self._deferredAssignments)
|
||||
# Set _deferredAssignments to None so that deferAssignment will fail
|
||||
# noisly if called after we've run through the deferred assignments.
|
||||
self._deferredAssignments = None
|
||||
del self.scopeStack[1:]
|
||||
self.popScope()
|
||||
self.check_dead_scopes()
|
||||
|
||||
|
||||
def deferFunction(self, callable):
|
||||
'''
|
||||
Schedule a function handler to be called just before completion.
|
||||
|
||||
This is used for handling function bodies, which must be deferred
|
||||
because code later in the file might modify the global scope. When
|
||||
`callable` is called, the scope at the time this is called will be
|
||||
restored, however it will contain any new bindings added to it.
|
||||
'''
|
||||
self._deferredFunctions.append((callable, self.scopeStack[:]))
|
||||
|
||||
|
||||
def deferAssignment(self, callable):
|
||||
"""
|
||||
Schedule an assignment handler to be called just after deferred
|
||||
function handlers.
|
||||
"""
|
||||
self._deferredAssignments.append((callable, self.scopeStack[:]))
|
||||
|
||||
|
||||
def _runDeferred(self, deferred):
|
||||
"""
|
||||
Run the callables in C{deferred} using their associated scope stack.
|
||||
"""
|
||||
for handler, scope in deferred:
|
||||
self.scopeStack = scope
|
||||
handler()
|
||||
|
||||
|
||||
def scope(self):
|
||||
return self.scopeStack[-1]
|
||||
scope = property(scope)
|
||||
|
||||
def popScope(self):
|
||||
self.dead_scopes.append(self.scopeStack.pop())
|
||||
|
||||
|
||||
def check_dead_scopes(self):
|
||||
"""
|
||||
Look at scopes which have been fully examined and report names in them
|
||||
which were imported but unused.
|
||||
"""
|
||||
for scope in self.dead_scopes:
|
||||
export = isinstance(scope.get('__all__'), ExportBinding)
|
||||
if export:
|
||||
all = scope['__all__'].names()
|
||||
if os.path.split(self.filename)[1] != '__init__.py':
|
||||
# Look for possible mistakes in the export list
|
||||
undefined = set(all) - set(scope)
|
||||
for name in undefined:
|
||||
self.report(
|
||||
messages.UndefinedExport,
|
||||
scope['__all__'].source.lineno,
|
||||
name)
|
||||
else:
|
||||
all = []
|
||||
|
||||
# Look for imported names that aren't used.
|
||||
for importation in scope.itervalues():
|
||||
if isinstance(importation, Importation):
|
||||
if not importation.used and importation.name not in all:
|
||||
self.report(
|
||||
messages.UnusedImport,
|
||||
importation.source.lineno,
|
||||
importation.name)
|
||||
|
||||
|
||||
def pushFunctionScope(self):
|
||||
self.scopeStack.append(FunctionScope())
|
||||
|
||||
def pushClassScope(self):
|
||||
self.scopeStack.append(ClassScope())
|
||||
|
||||
def report(self, messageClass, *args, **kwargs):
|
||||
self.messages.append(messageClass(self.filename, *args, **kwargs))
|
||||
|
||||
def handleChildren(self, tree):
|
||||
for node in iter_child_nodes(tree):
|
||||
self.handleNode(node, tree)
|
||||
|
||||
def isDocstring(self, node):
|
||||
"""
|
||||
Determine if the given node is a docstring, as long as it is at the
|
||||
correct place in the node tree.
|
||||
"""
|
||||
return isinstance(node, _ast.Str) or \
|
||||
(isinstance(node, _ast.Expr) and
|
||||
isinstance(node.value, _ast.Str))
|
||||
|
||||
def handleNode(self, node, parent):
|
||||
node.parent = parent
|
||||
if self.traceTree:
|
||||
print ' ' * self.nodeDepth + node.__class__.__name__
|
||||
self.nodeDepth += 1
|
||||
if self.futuresAllowed and not \
|
||||
(isinstance(node, _ast.ImportFrom) or self.isDocstring(node)):
|
||||
self.futuresAllowed = False
|
||||
nodeType = node.__class__.__name__.upper()
|
||||
try:
|
||||
handler = getattr(self, nodeType)
|
||||
handler(node)
|
||||
finally:
|
||||
self.nodeDepth -= 1
|
||||
if self.traceTree:
|
||||
print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__
|
||||
|
||||
def ignore(self, node):
|
||||
pass
|
||||
|
||||
# "stmt" type nodes
|
||||
RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \
|
||||
TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren
|
||||
|
||||
CONTINUE = BREAK = PASS = ignore
|
||||
|
||||
# "expr" type nodes
|
||||
BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \
|
||||
CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren
|
||||
|
||||
NUM = STR = ELLIPSIS = ignore
|
||||
|
||||
# "slice" type nodes
|
||||
SLICE = EXTSLICE = INDEX = handleChildren
|
||||
|
||||
# expression contexts are node instances too, though being constants
|
||||
LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore
|
||||
|
||||
# same for operators
|
||||
AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \
|
||||
BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \
|
||||
EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
|
||||
|
||||
# additional node types
|
||||
COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren
|
||||
|
||||
def addBinding(self, lineno, value, reportRedef=True):
|
||||
'''Called when a binding is altered.
|
||||
|
||||
- `lineno` is the line 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,
|
||||
lineno, value.name, self.scope[value.name].source.lineno)
|
||||
|
||||
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,
|
||||
lineno, value.name, scope[value.name].source.lineno)
|
||||
|
||||
if isinstance(value, UnBinding):
|
||||
try:
|
||||
del self.scope[value.name]
|
||||
except KeyError:
|
||||
self.report(messages.UndefinedName, lineno, value.name)
|
||||
else:
|
||||
self.scope[value.name] = value
|
||||
|
||||
def GLOBAL(self, node):
|
||||
"""
|
||||
Keep track of globals declarations.
|
||||
"""
|
||||
if isinstance(self.scope, FunctionScope):
|
||||
self.scope.globals.update(dict.fromkeys(node.names))
|
||||
|
||||
def LISTCOMP(self, node):
|
||||
# handle generators before element
|
||||
for gen in node.generators:
|
||||
self.handleNode(gen, node)
|
||||
self.handleNode(node.elt, node)
|
||||
|
||||
GENERATOREXP = SETCOMP = LISTCOMP
|
||||
|
||||
# dictionary comprehensions; introduced in Python 2.7
|
||||
def DICTCOMP(self, node):
|
||||
for gen in node.generators:
|
||||
self.handleNode(gen, node)
|
||||
self.handleNode(node.key, node)
|
||||
self.handleNode(node.value, node)
|
||||
|
||||
def FOR(self, node):
|
||||
"""
|
||||
Process bindings for loop variables.
|
||||
"""
|
||||
vars = []
|
||||
def collectLoopVars(n):
|
||||
if isinstance(n, _ast.Name):
|
||||
vars.append(n.id)
|
||||
elif isinstance(n, _ast.expr_context):
|
||||
return
|
||||
else:
|
||||
for c in iter_child_nodes(n):
|
||||
collectLoopVars(c)
|
||||
|
||||
collectLoopVars(node.target)
|
||||
for varn in vars:
|
||||
if (isinstance(self.scope.get(varn), Importation)
|
||||
# unused ones will get an unused import warning
|
||||
and self.scope[varn].used):
|
||||
self.report(messages.ImportShadowedByLoopVar,
|
||||
node.lineno, varn, self.scope[varn].source.lineno)
|
||||
|
||||
self.handleChildren(node)
|
||||
|
||||
def NAME(self, node):
|
||||
"""
|
||||
Handle occurrence of Name (which can be a load/store/delete access.)
|
||||
"""
|
||||
# Locate the name in locals / function / globals scopes.
|
||||
if isinstance(node.ctx, (_ast.Load, _ast.AugLoad)):
|
||||
# try local scope
|
||||
importStarred = self.scope.importStarred
|
||||
try:
|
||||
self.scope[node.id].used = (self.scope, node.lineno)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
||||
# try enclosing function scopes
|
||||
|
||||
for scope in self.scopeStack[-2:0:-1]:
|
||||
importStarred = importStarred or scope.importStarred
|
||||
if not isinstance(scope, FunctionScope):
|
||||
continue
|
||||
try:
|
||||
scope[node.id].used = (self.scope, node.lineno)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
||||
# try global scope
|
||||
|
||||
importStarred = importStarred or self.scopeStack[0].importStarred
|
||||
try:
|
||||
self.scopeStack[0][node.id].used = (self.scope, node.lineno)
|
||||
except KeyError:
|
||||
if ((not hasattr(__builtin__, node.id))
|
||||
and node.id not in _MAGIC_GLOBALS
|
||||
and not importStarred):
|
||||
if (os.path.basename(self.filename) == '__init__.py' and
|
||||
node.id == '__path__'):
|
||||
# the special name __path__ is valid only in packages
|
||||
pass
|
||||
else:
|
||||
self.report(messages.UndefinedName, node.lineno, node.id)
|
||||
elif isinstance(node.ctx, (_ast.Store, _ast.AugStore)):
|
||||
# if the name hasn't already been defined in the current scope
|
||||
if isinstance(self.scope, FunctionScope) and node.id not in self.scope:
|
||||
# for each function or module scope above us
|
||||
for scope in self.scopeStack[:-1]:
|
||||
if not isinstance(scope, (FunctionScope, ModuleScope)):
|
||||
continue
|
||||
# if the name was defined in that scope, and the name has
|
||||
# been accessed already in the current scope, and hasn't
|
||||
# been declared global
|
||||
if (node.id in scope
|
||||
and scope[node.id].used
|
||||
and scope[node.id].used[0] is self.scope
|
||||
and node.id not in self.scope.globals):
|
||||
# then it's probably a mistake
|
||||
self.report(messages.UndefinedLocal,
|
||||
scope[node.id].used[1],
|
||||
node.id,
|
||||
scope[node.id].source.lineno)
|
||||
break
|
||||
|
||||
if isinstance(node.parent,
|
||||
(_ast.For, _ast.comprehension, _ast.Tuple, _ast.List)):
|
||||
binding = Binding(node.id, node)
|
||||
elif (node.id == '__all__' and
|
||||
isinstance(self.scope, ModuleScope)):
|
||||
binding = ExportBinding(node.id, node.parent.value)
|
||||
else:
|
||||
binding = Assignment(node.id, node)
|
||||
if node.id in self.scope:
|
||||
binding.used = self.scope[node.id].used
|
||||
self.addBinding(node.lineno, 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.lineno, UnBinding(node.id, node))
|
||||
else:
|
||||
# must be a Param context -- this only happens for names in function
|
||||
# arguments, but these aren't dispatched through here
|
||||
raise RuntimeError(
|
||||
"Got impossible expression context: %r" % (node.ctx,))
|
||||
|
||||
|
||||
def FUNCTIONDEF(self, node):
|
||||
# the decorators attribute is called decorator_list as of Python 2.6
|
||||
if hasattr(node, 'decorators'):
|
||||
for deco in node.decorators:
|
||||
self.handleNode(deco, node)
|
||||
else:
|
||||
for deco in node.decorator_list:
|
||||
self.handleNode(deco, node)
|
||||
self.addBinding(node.lineno, 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.lineno, 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.lineno, 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.lineno, 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.lineno, Binding(node.name, node))
|
||||
|
||||
def ASSIGN(self, node):
|
||||
self.handleNode(node.value, node)
|
||||
for target in node.targets:
|
||||
self.handleNode(target, node)
|
||||
|
||||
def AUGASSIGN(self, node):
|
||||
# AugAssign is awkward: must set the context explicitly and visit twice,
|
||||
# once with AugLoad context, once with AugStore context
|
||||
node.target.ctx = _ast.AugLoad()
|
||||
self.handleNode(node.target, node)
|
||||
self.handleNode(node.value, node)
|
||||
node.target.ctx = _ast.AugStore()
|
||||
self.handleNode(node.target, node)
|
||||
|
||||
def IMPORT(self, node):
|
||||
for alias in node.names:
|
||||
name = alias.asname or alias.name
|
||||
importation = Importation(name, node)
|
||||
self.addBinding(node.lineno, importation)
|
||||
|
||||
def IMPORTFROM(self, node):
|
||||
if node.module == '__future__':
|
||||
if not self.futuresAllowed:
|
||||
self.report(messages.LateFutureImport, node.lineno,
|
||||
[n.name for n in node.names])
|
||||
else:
|
||||
self.futuresAllowed = False
|
||||
|
||||
for alias in node.names:
|
||||
if alias.name == '*':
|
||||
self.scope.importStarred = True
|
||||
self.report(messages.ImportStarUsed, node.lineno, node.module)
|
||||
continue
|
||||
name = alias.asname or alias.name
|
||||
importation = Importation(name, node)
|
||||
if node.module == '__future__':
|
||||
importation.used = (self.scope, node.lineno)
|
||||
self.addBinding(node.lineno, importation)
|
@@ -0,0 +1,94 @@
|
||||
# (c) 2005 Divmod, Inc. See LICENSE file for details
|
||||
|
||||
class Message(object):
|
||||
message = ''
|
||||
message_args = ()
|
||||
def __init__(self, filename, lineno):
|
||||
self.filename = filename
|
||||
self.lineno = lineno
|
||||
def __str__(self):
|
||||
return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args)
|
||||
|
||||
|
||||
class UnusedImport(Message):
|
||||
message = '%r imported but unused'
|
||||
def __init__(self, filename, lineno, name):
|
||||
Message.__init__(self, filename, lineno)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
class RedefinedWhileUnused(Message):
|
||||
message = 'redefinition of unused %r from line %r'
|
||||
def __init__(self, filename, lineno, name, orig_lineno):
|
||||
Message.__init__(self, filename, lineno)
|
||||
self.message_args = (name, orig_lineno)
|
||||
|
||||
|
||||
class ImportShadowedByLoopVar(Message):
|
||||
message = 'import %r from line %r shadowed by loop variable'
|
||||
def __init__(self, filename, lineno, name, orig_lineno):
|
||||
Message.__init__(self, filename, lineno)
|
||||
self.message_args = (name, orig_lineno)
|
||||
|
||||
|
||||
class ImportStarUsed(Message):
|
||||
message = "'from %s import *' used; unable to detect undefined names"
|
||||
def __init__(self, filename, lineno, modname):
|
||||
Message.__init__(self, filename, lineno)
|
||||
self.message_args = (modname,)
|
||||
|
||||
|
||||
class UndefinedName(Message):
|
||||
message = 'undefined name %r'
|
||||
def __init__(self, filename, lineno, name):
|
||||
Message.__init__(self, filename, lineno)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
|
||||
class UndefinedExport(Message):
|
||||
message = 'undefined name %r in __all__'
|
||||
def __init__(self, filename, lineno, name):
|
||||
Message.__init__(self, filename, lineno)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
|
||||
class UndefinedLocal(Message):
|
||||
message = "local variable %r (defined in enclosing scope on line %r) referenced before assignment"
|
||||
def __init__(self, filename, lineno, name, orig_lineno):
|
||||
Message.__init__(self, filename, lineno)
|
||||
self.message_args = (name, orig_lineno)
|
||||
|
||||
|
||||
class DuplicateArgument(Message):
|
||||
message = 'duplicate argument %r in function definition'
|
||||
def __init__(self, filename, lineno, name):
|
||||
Message.__init__(self, filename, lineno)
|
||||
self.message_args = (name,)
|
||||
|
||||
|
||||
class RedefinedFunction(Message):
|
||||
message = 'redefinition of function %r from line %r'
|
||||
def __init__(self, filename, lineno, name, orig_lineno):
|
||||
Message.__init__(self, filename, lineno)
|
||||
self.message_args = (name, orig_lineno)
|
||||
|
||||
|
||||
class LateFutureImport(Message):
|
||||
message = 'future import(s) %r after other statements'
|
||||
def __init__(self, filename, lineno, names):
|
||||
Message.__init__(self, filename, lineno)
|
||||
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, lineno, names):
|
||||
Message.__init__(self, filename, lineno)
|
||||
self.message_args = (names,)
|
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# lua.py - sublimelint package for checking lua files
|
||||
|
||||
import re
|
||||
from base_linter import BaseLinter
|
||||
|
||||
CONFIG = {
|
||||
'language': 'Lua',
|
||||
'executable': 'luac',
|
||||
'lint_args': ['-p', '-'],
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
for line in errors.splitlines():
|
||||
match = re.match(r'^.+:(?P<line>\d+):\s+(?P<error>.+)', line)
|
||||
|
||||
if match:
|
||||
error, line = match.group('error'), match.group('line')
|
||||
self.add_message(int(line), lines, error, errorMessages)
|
@@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''notes.py
|
||||
|
||||
Used to highlight user-defined "annotations" such as TODO, README, etc.,
|
||||
depending user choice.
|
||||
'''
|
||||
|
||||
import sublime
|
||||
|
||||
from base_linter import BaseLinter
|
||||
|
||||
CONFIG = {
|
||||
'language': 'Annotations'
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
DEFAULT_NOTES = ["TODO", "README", "FIXME"]
|
||||
|
||||
def built_in_check(self, view, code, filename):
|
||||
annotations = self.select_annotations(view)
|
||||
regions = []
|
||||
|
||||
for annotation in annotations:
|
||||
regions.extend(self.find_all(code, annotation, view))
|
||||
|
||||
return regions
|
||||
|
||||
def select_annotations(self, view):
|
||||
'''selects the list of annotations to use'''
|
||||
return view.settings().get("annotations", self.DEFAULT_NOTES)
|
||||
|
||||
def extract_annotations(self, code, view, filename):
|
||||
'''extract all lines with annotations'''
|
||||
annotations = self.select_annotations(view)
|
||||
annotation_starts = []
|
||||
|
||||
for annotation in annotations:
|
||||
start = 0
|
||||
length = len(annotation)
|
||||
|
||||
while True:
|
||||
start = code.find(annotation, start)
|
||||
|
||||
if start != -1:
|
||||
end = start + length
|
||||
annotation_starts.append(start)
|
||||
start = end
|
||||
else:
|
||||
break
|
||||
|
||||
regions_with_notes = set([])
|
||||
|
||||
for point in annotation_starts:
|
||||
regions_with_notes.add(view.extract_scope(point))
|
||||
|
||||
regions_with_notes = sorted(list(regions_with_notes))
|
||||
text = []
|
||||
|
||||
for region in regions_with_notes:
|
||||
row, col = view.rowcol(region.begin())
|
||||
text.append("[{0}:{1}]".format(filename, row + 1))
|
||||
text.append(view.substr(region))
|
||||
|
||||
return '\n'.join(text)
|
||||
|
||||
def find_all(self, text, string, view):
|
||||
''' finds all occurences of "string" in "text" and notes their positions
|
||||
as a sublime Region
|
||||
'''
|
||||
found = []
|
||||
length = len(string)
|
||||
start = 0
|
||||
|
||||
while True:
|
||||
start = text.find(string, start)
|
||||
|
||||
if start != -1:
|
||||
end = start + length
|
||||
found.append(sublime.Region(start, end))
|
||||
start = end
|
||||
else:
|
||||
break
|
||||
|
||||
return found
|
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# objective-j.py - Lint checking for Objective-J - 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 of 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.
|
||||
#
|
||||
|
||||
from capp_lint import LintChecker
|
||||
from base_linter import BaseLinter
|
||||
|
||||
CONFIG = {
|
||||
'language': 'Objective-J'
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
def built_in_check(self, view, code, filename):
|
||||
checker = LintChecker(view)
|
||||
checker.lint_text(code, filename)
|
||||
return checker.errors
|
||||
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
for error in errors:
|
||||
lineno = error['lineNum']
|
||||
self.add_message(lineno, lines, error['message'], errorMessages if type == LintChecker.ERROR_TYPE_ILLEGAL else warningMessages)
|
||||
|
||||
for position in error.get('positions', []):
|
||||
self.underline_range(view, lineno, position, errorUnderlines if type == LintChecker.ERROR_TYPE_ILLEGAL else warningUnderlines)
|
@@ -0,0 +1,65 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# perl.py - sublimelint package for checking perl files
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from base_linter import BaseLinter
|
||||
|
||||
CONFIG = {
|
||||
'language': 'Perl'
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
PERLCRITIC_RE = re.compile(r'\[(?P<pbp>.+)\] (?P<error>.+?) at line (?P<line>\d+), column (?P<column>\d+).+?')
|
||||
PERL_RE = re.compile(r'(?P<error>.+?) at .+? line (?P<line>\d+)(, near "(?P<near>.+?)")?')
|
||||
|
||||
def __init__(self, config):
|
||||
super(Linter, self).__init__(config)
|
||||
self.linter = None
|
||||
|
||||
def get_executable(self, view):
|
||||
self.linter = view.settings().get('perl_linter', 'perlcritic')
|
||||
|
||||
if self.linter == 'perl':
|
||||
linter_name = 'Perl'
|
||||
else:
|
||||
linter_name = 'Perl::Critic'
|
||||
|
||||
try:
|
||||
path = self.get_mapped_executable(view, self.linter)
|
||||
subprocess.call([path, '--version'], startupinfo=self.get_startupinfo())
|
||||
return (True, path, 'using {0}'.format(linter_name))
|
||||
except OSError:
|
||||
return (False, '', '{0} is required'.format(linter_name))
|
||||
|
||||
def get_lint_args(self, view, code, filename):
|
||||
if self.linter == 'perl':
|
||||
return ['-c']
|
||||
else:
|
||||
return ['--verbose', '8']
|
||||
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
for line in errors.splitlines():
|
||||
if self.linter == 'perl':
|
||||
match = self.PERL_RE.match(line)
|
||||
else:
|
||||
match = self.PERLCRITIC_RE.match(line)
|
||||
|
||||
if match:
|
||||
error, line = match.group('error'), match.group('line')
|
||||
lineno = int(line)
|
||||
|
||||
if self.linter == 'perl':
|
||||
near = match.group('near')
|
||||
|
||||
if near:
|
||||
error = '{0}, near "{1}"'.format(error, near)
|
||||
self.underline_regex(view, lineno, '(?P<underline>{0})'.format(re.escape(near)), lines, errorUnderlines)
|
||||
else:
|
||||
column = match.group('column')
|
||||
column = int(column) - 1
|
||||
self.underline_word(view, lineno, column, errorUnderlines)
|
||||
|
||||
self.add_message(lineno, lines, error, errorMessages)
|
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# php.py - sublimelint package for checking php files
|
||||
|
||||
import re
|
||||
|
||||
from base_linter import BaseLinter
|
||||
|
||||
CONFIG = {
|
||||
'language': 'PHP',
|
||||
'executable': 'php',
|
||||
'lint_args': ['-l', '-d display_errors=On', '-d log_errors=Off']
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
for line in errors.splitlines():
|
||||
match = re.match(r'^Parse error:\s*(?:\w+ error,\s*)?(?P<error>.+?)\s+in\s+.+?\s*line\s+(?P<line>\d+)', line)
|
||||
|
||||
if match:
|
||||
error, line = match.group('error'), match.group('line')
|
||||
self.add_message(int(line), lines, error, errorMessages)
|
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# puppet.py - sublimelint package for checking puppet files
|
||||
|
||||
import re
|
||||
|
||||
from base_linter import BaseLinter, INPUT_METHOD_TEMP_FILE
|
||||
|
||||
CONFIG = {
|
||||
'language': 'Puppet',
|
||||
'executable': 'puppet',
|
||||
'lint_args': ['parser', 'validate', '--color=false', '{filename}'],
|
||||
'test_existence_args': '-V',
|
||||
'input_method': INPUT_METHOD_TEMP_FILE
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
for line in errors.splitlines():
|
||||
match = re.match(r'[Ee]rr(or)?: (?P<error>.+?(Syntax error at \'(?P<near>.+?)\'; expected \'.+\')) at /.+?:(?P<line>\d+)?', line)
|
||||
if not match:
|
||||
match = re.match(r'[Ee]rr(or)?: (?P<error>.+?(Could not match (?P<near>.+?))?) at /.+?:(?P<line>\d+)?', line)
|
||||
|
||||
if match:
|
||||
error, line = match.group('error'), match.group('line')
|
||||
lineno = int(line)
|
||||
near = match.group('near')
|
||||
|
||||
if near:
|
||||
error = '{0}, near "{1}"'.format(error, near)
|
||||
self.underline_regex(view, lineno, '(?P<underline>{0})'.format(re.escape(near)), lines, errorUnderlines)
|
||||
|
||||
self.add_message(lineno, lines, error, errorMessages)
|
@@ -0,0 +1,265 @@
|
||||
# -*- 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)
|
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ruby.py - sublimelint package for checking ruby files
|
||||
|
||||
import re
|
||||
|
||||
from base_linter import BaseLinter
|
||||
|
||||
CONFIG = {
|
||||
'language': 'Ruby',
|
||||
'executable': 'ruby',
|
||||
'lint_args': '-wc'
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
for line in errors.splitlines():
|
||||
match = re.match(r'^.+:(?P<line>\d+):\s+(?P<error>.+)', line)
|
||||
|
||||
if match:
|
||||
error, line = match.group('error'), match.group('line')
|
||||
self.add_message(int(line), lines, error, errorMessages)
|
@@ -0,0 +1,83 @@
|
||||
''' sublime_pylint.py - sublimelint package for checking python files
|
||||
|
||||
pylint is not available as a checker that runs in the background
|
||||
as it generally takes much too long.
|
||||
'''
|
||||
|
||||
from StringIO import StringIO
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
from pylint import checkers
|
||||
from pylint import lint
|
||||
PYLINT_AVAILABLE = True
|
||||
except ImportError:
|
||||
PYLINT_AVAILABLE = False
|
||||
|
||||
from base_linter import BaseLinter
|
||||
|
||||
CONFIG = {
|
||||
'language': 'pylint'
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
def get_executable(self, view):
|
||||
return (PYLINT_AVAILABLE, None, 'built in' if PYLINT_AVAILABLE else 'the pylint module could not be imported')
|
||||
|
||||
def built_in_check(self, view, code, filename):
|
||||
linter = lint.PyLinter()
|
||||
checkers.initialize(linter)
|
||||
|
||||
# Disable some errors.
|
||||
linter.load_command_line_configuration([
|
||||
'--module-rgx=.*', # don't check the module name
|
||||
'--reports=n', # remove tables
|
||||
'--persistent=n', # don't save the old score (no sense for temp)
|
||||
])
|
||||
|
||||
temp = tempfile.NamedTemporaryFile(suffix='.py')
|
||||
temp.write(code)
|
||||
temp.flush()
|
||||
|
||||
output_buffer = StringIO()
|
||||
linter.reporter.set_output(output_buffer)
|
||||
linter.check(temp.name)
|
||||
report = output_buffer.getvalue().replace(temp.name, 'line ')
|
||||
|
||||
output_buffer.close()
|
||||
temp.close()
|
||||
|
||||
return report
|
||||
|
||||
def remove_unwanted(self, errors):
|
||||
'''remove unwanted warnings'''
|
||||
## todo: investigate how this can be set by a user preference
|
||||
# as it appears that the user pylint configuration file is ignored.
|
||||
lines = errors.split('\n')
|
||||
wanted = []
|
||||
unwanted = ["Found indentation with tabs instead of spaces",
|
||||
"************* Module"]
|
||||
|
||||
for line in lines:
|
||||
for not_include in unwanted:
|
||||
if not_include in line:
|
||||
break
|
||||
else:
|
||||
wanted.append(line)
|
||||
|
||||
return '\n'.join(wanted)
|
||||
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
errors = self.remove_unwanted(errors)
|
||||
|
||||
for line in errors.splitlines():
|
||||
info = line.split(":")
|
||||
|
||||
try:
|
||||
lineno = info[1]
|
||||
except IndexError:
|
||||
print info
|
||||
|
||||
message = ":".join(info[2:])
|
||||
self.add_message(int(lineno), lines, message, errorMessages)
|
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# xml.py - sublimelint package for checking xml files
|
||||
|
||||
import re
|
||||
|
||||
from base_linter import BaseLinter
|
||||
|
||||
CONFIG = {
|
||||
'language': 'XML',
|
||||
'executable': 'xmllint',
|
||||
'lint_args': ['-noout', '-']
|
||||
}
|
||||
|
||||
|
||||
class Linter(BaseLinter):
|
||||
def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
|
||||
|
||||
for line in errors.splitlines():
|
||||
match = re.match(r'\-\:(?P<line>\d+): (?P<error>.+)', line)
|
||||
|
||||
if match:
|
||||
error, line = match.group('error'), match.group('line')
|
||||
self.add_message(int(line), lines, error, errorMessages)
|
Reference in New Issue
Block a user