feat(SublimeText2.WebPackages): cache packages

This commit is contained in:
Iristyle
2013-04-04 08:54:25 -04:00
parent 590d7a44f9
commit 1e6f643a1b
1026 changed files with 79077 additions and 0 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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)

View File

@@ -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,)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)