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,9 @@
{
"Python": {
"pythonExtraPaths": [
"libs",
"~/Applications/Sublime Text 2.app/Contents/MacOS",
"/Applications/Sublime Text 2.app/Contents/MacOS"
]
}
}

View File

@@ -0,0 +1,8 @@
.git
.hg
.svn
*.pyc
*.pyo
.DS_Store

View File

@@ -0,0 +1,5 @@
[
{ "keys": ["ctrl+alt+l"], "command": "sublimelinter", "args": {"action": "lint"} },
{ "keys": ["ctrl+alt+e"], "command": "find_next_lint_error" },
{ "keys": ["ctrl+alt+shift+e"], "command": "find_previous_lint_error" }
]

View File

@@ -0,0 +1,5 @@
[
{ "keys": ["ctrl+super+l"], "command": "sublimelinter", "args": {"action": "lint"} },
{ "keys": ["ctrl+super+e"], "command": "find_next_lint_error" },
{ "keys": ["ctrl+super+shift+e"], "command": "find_previous_lint_error" }
]

View File

@@ -0,0 +1,5 @@
[
{ "keys": ["ctrl+alt+l"], "command": "sublimelinter", "args": {"action": "lint"} },
{ "keys": ["ctrl+alt+e"], "command": "find_next_lint_error" },
{ "keys": ["ctrl+alt+shift+e"], "command": "find_previous_lint_error" }
]

View File

@@ -0,0 +1,42 @@
[
{
"caption": "SublimeLinter: Lint Current File",
"command": "sublimelinter_lint",
"args": {"action": "lint"}
},
{
"caption": "SublimeLinter: Show Error List",
"command": "sublimelinter_show_errors",
"args": {"action": "lint", "show_popup": true}
},
{
"caption": "SublimeLinter: Background Linting",
"command": "sublimelinter_lint",
"args": {"action": "on"}
},
{
"caption": "SublimeLinter: Load-Save Linting",
"command": "sublimelinter_enable_load_save",
"args": {"action": "load-save"}
},
{
"caption": "SublimeLinter: Save-Only Linting",
"command": "sublimelinter_enable_save_only",
"args": {"action": "save-only"}
},
{
"caption": "SublimeLinter: Disable Linting",
"command": "sublimelinter_disable",
"args": {"action": "off"}
},
{
"caption": "SublimeLinter: Extract Annotations",
"command": "sublimelinter_annotations",
"args": {}
},
{
"caption": "SublimeLinter: Reset",
"command": "sublimelinter_lint",
"args": {"action": "reset"}
}
]

View File

@@ -0,0 +1,20 @@
Copyright Germán M. Bravo, Aparajita Fishman, Jacob Swartwood, and other
contributors.
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.

View File

@@ -0,0 +1,99 @@
[
{
"caption": "Preferences",
"mnemonic": "n",
"id": "preferences",
"children":
[
{
"caption": "Package Settings",
"mnemonic": "P",
"id": "package-settings",
"children":
[
{
"caption": "SublimeLinter",
"children":
[
{
"command": "open_file",
"args": {"file": "${packages}/SublimeLinter/README.md"},
"caption": "README"
},
{
"command": "open_file",
"args": {"file": "${packages}/SublimeLinter/changelog.txt"},
"caption": "Change Log"
},
{ "caption": "-" },
{
"command": "open_file",
"args": {"file": "${packages}/SublimeLinter/SublimeLinter.sublime-settings"},
"caption": "Settings Default"
},
{
"command": "open_file",
"args": {"file": "${packages}/User/SublimeLinter.sublime-settings"},
"caption": "Settings User"
},
{
"command": "open_file_settings",
"caption": "Settings Syntax Specific User"
},
{ "caption": "-" },
{
"command": "open_file",
"args": {
"file": "${packages}/SublimeLinter/Default (OSX).sublime-keymap",
"platform": "OSX"
},
"caption": "Key Bindings Default"
},
{
"command": "open_file",
"args": {
"file": "${packages}/SublimeLinter/Default (Linux).sublime-keymap",
"platform": "Linux"
},
"caption": "Key Bindings Default"
},
{
"command": "open_file",
"args": {
"file": "${packages}/SublimeLinter/Default (Windows).sublime-keymap",
"platform": "Windows"
},
"caption": "Key Bindings Default"
},
{
"command": "open_file",
"args": {
"file": "${packages}/User/Default (OSX).sublime-keymap",
"platform": "OSX"
},
"caption": "Key Bindings User"
},
{
"command": "open_file",
"args": {
"file": "${packages}/User/Default (Linux).sublime-keymap",
"platform": "Linux"
},
"caption": "Key Bindings User"
},
{
"command": "open_file",
"args": {
"file": "${packages}/User/Default (Windows).sublime-keymap",
"platform": "Windows"
},
"caption": "Key Bindings User"
},
{ "caption": "-" }
]
}
]
}
]
}
]

View File

@@ -0,0 +1,398 @@
SublimeLinter
=============
SublimeLinter is a plugin that supports "lint" programs (known as "linters"). SublimeLinter highlights
lines of code the linter deems to contain (potential) errors. It also
supports highlighting special annotations (for example: TODO) so that they
can be quickly located.
SublimeLinter has built in linters for the following languages:
* C/C++ - lint via `cppcheck`
* CoffeeScript - lint via `coffee -s -l`
* CSS - lint via built-in [csslint](http://csslint.net)
* Git Commit Messages - lint via built-in module based on [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
* Haml - syntax check via `haml -c`
* HTML - lint via `tidy` (actually [tidy for HTML5](http://w3c.github.com/tidy-html5/))
* Java - lint via `javac -Xlint`
* Javascript - lint via built in [jshint](http://jshint.org), [jslint](http://jslint.com), or the [closure linter (gjslint)](https://developers.google.com/closure/utilities/docs/linter_howto) (if installed)
* Lua - syntax check via `luac`
* Objective-J - lint via built-in [capp_lint](https://github.com/aparajita/capp_lint)
* Perl - lint via [Perl:Critic](http://perlcritic.com/) or syntax+deprecation check via `perl -c`
* PHP - syntax check via `php -l`
* Puppet - syntax check via `puppet parser validate`
* Python - native, moderately-complete lint
* Ruby - syntax check via `ruby -wc`
* XML - lint via `xmllint`
Installing
----------
**With the Package Control plugin:** The easiest way to install SublimeLinter is through Package Control, which can be found at this site: http://wbond.net/sublime_packages/package_control
Once you install Package Control, restart ST2 and bring up the Command Palette (`Command+Shift+P` on OS X, `Control+Shift+P` on Linux/Windows). Select "Package Control: Install Package", wait while Package Control fetches the latest package list, then select SublimeLinter when the list appears. The advantage of using this method is that Package Control will automatically keep SublimeLinter up to date with the latest version.
**Without Git:** Download the latest source from [GitHub](https://github.com/SublimeLinter/SublimeLinter) and copy the SublimeLinter folder to your Sublime Text "Packages" directory.
**With Git:** Clone the repository in your Sublime Text "Packages" directory:
git clone https://github.com/SublimeLinter/SublimeLinter.git
The "Packages" directory is located at:
* OS X:
~/Library/Application Support/Sublime Text 2/Packages/
* Linux:
~/.config/sublime-text-2/Packages/
* Windows:
%APPDATA%/Sublime Text 2/Packages/
### Javascript-based linters
If you plan to edit files that use a Javascript-based linter (Javascript, CSS), your system
must have a Javascript engine installed. Mac OS X comes with a preinstalled Javascript engine called
JavaScriptCore, which is used if Node.js is not installed. On Windows, you **must** install the
Javascript engine Node.js, which can be downloaded from [the Node.js site](http://nodejs.org/#download).
On Mac OS X, you **must** install Node.js if you plan to edit Javascript or CSS files that
use non-ASCII characters in strings or comments, because JavaScriptCore is not Unicode-aware.
After installing Node.js, if the Node.js executable ("node" on Mac OS X, "node.exe" on Windows)
cannot be found by SublimeLinter, you may have to set the path to the executable in the
"sublimelinter\_executable\_map" setting. See the "Configuring" section below for info on
SublimeLinter settings.
Configuring
-----------
There are a number of settings available to customize the behavior of SublimeLinter and its linters. For the latest information on what settings are available, select the menu item `Preferences->Package Settings->SublimeLinter->Settings - Default`.
Do **NOT** edit the default SublimeLinter settings. Your changes will be lost when SublimeLinter is updated. ALWAYS edit the user SublimeLinter settings by selecting `Preferences->Package Settings->SublimeLinter->Settings - User`. Note that individual settings you include in your user settings will _completely_ replace the corresponding default setting, so you must provide that setting in its entirety.
### Linter-specific notes
Following are notes specific to individual linters that you should be aware of:
* **C/C++** - The default C/C++ linter is [cppcheck](http://cppcheck.sourceforge.net/), however Google's [cpplint.py](http://google-styleguide.googlecode.com/svn/trunk/cpplint/) is also supported.
To swap `cppcheck` out for `cpplint.py` you will need to adjust `sublimelinter_syntax_map` and possibly `sublimelinter_executable_map` also. First change the _linter language_ for `C` and `C++` to `c_cpplint` via `sublimelinter_syntax_map`. If `cpplint.py` is not on your system `PATH`, then add an entry for `c_cpplint` into `sublimelinter_executable_map` with the path to the file. As usual add these adjustments to the SublimeLinter **User Settings** file. An example:
"sublimelinter_syntax_map":
{
"Python Django": "python",
"Ruby on Rails": "ruby",
"C++": "c_cpplint",
"C": "c_cpplint"
},
"sublimelinter_executable_map":
{
"c_cpplint": "/Users/[my username]/Desktop/cpplint.py"
}
* **CSS** - This linter runs [csslint](http://csslint.net). This linter requires a Javascript engine (like Node.js) to be installed (see notes above for the JavaScript linters: "jshint" or "jslint").
By default all CSSLint settings are turned on. You may customize CSSLint behavior with the "csslint_options" setting. Please select `Preferences->Package Settings->SublimeLinter->Settings - Default` for more information on turning off or adjusting severity of tests. For more information about options available to CSSLint, see https://github.com/stubbornella/csslint/wiki/Rules.
* **HTML** - This linter will not run unless you have a version of tidy with HTML5 support. To use this linter, please see: https://github.com/w3c/tidy-html5
* **Java** - Because it uses `javac` to do linting, each time you run the linter the entire dependency graph of the current file will be checked. Depending on the number of classes you import, this can be **extremely** slow. Also note that you **must** provide the `-sourcepath`, `-classpath`, `-Xlint` and `{filename}` arguments to `javac` in your per-project settings. See "Per-project settings" below for more information.
* **JavaScript** - If the "javascript_linter" setting is "jshint" or "jslint", this linter runs [jshint](http://jshint.org) (or [jslint](http://jslint.com) respectively) using Node.js. See "Javascript-based linters" above for information on how to install Node.js.
If the "javascript_linter" setting is "gjslint", this linter runs the [closure linter (gjslint)](https://developers.google.com/closure/utilities/docs/linter_howto). After installation, if gjslint cannot be found by SublimeLinter, you may have to set the path to gjslint in the "sublimelinter\_executable\_map" setting.
You may want to modify the options passed to jshint, jslint, or gjslint. This can be done by using the **jshint_options**, **jslint_options**, or **gjslint_options** setting. Refer to the jshint.org site, the jslint.com site, or run `gjslint --help` for more information on the configuration options available.
SublimeLinter supports `.jshintrc` files. If using JSHint, SublimeLinter will recursively search the directory tree (from the file location to the file-system root directory). This functionality is specified in the [JSHint README](https://github.com/jshint/node-jshint/#within-your-projects-directory-tree).
* **Perl** - Due to a vulnerability (issue [#77](https://github.com/SublimeLinter/SublimeLinter/issues/77)) with the Perl linter, Perl syntax checking is no longer enabled by default. The default linter for Perl has been replaced by Perl::Critic. The standard Perl syntax checker can still be invoked by switching the "perl_linter" setting to "perl".
* **Ruby** - If you are using rvm or rbenv, you will probably have to specify the full path to the ruby you are using in the "sublimelinter_executable_map" setting. See "Configuring" below for more info.
### Per-project settings
SublimeLinter supports per-project/per-language settings. This is useful if a linter requires path configuration on a per-project basis. To edit your project settings, select the menu item `Project->Edit Project`. If there is no "settings" object at the top level, add one and then add a "SublimeLinter" sub-object, like this:
{
"folders":
[
{
"path": "/Users/aparajita/Projects/foo/src"
}
],
"settings":
{
"SublimeLinter":
{
}
}
}
Within the "SublimeLinter" object, you can add a settings object for each language. The language name must match the language item in the linter's CONFIG object, which can be found in the linter's source file in the SublimeLinter/sublimelinter/modules folder. Each language can have two settings:
* "working_directory" - If present and a valid absolute directory path, the working directory is set to this path before the linter executes. This is useful if you are providing linter arguments that contain paths and you want to use working directory-relative paths instead of absolute paths.
* "lint_args" - If present, it must be a sequence of string arguments to pass to the linter. If your linter expects a filename as an argument, use the argument "{filename}" as a placeholder. Note that if you provide this item, you are responsible for passing **all** required arguments to the linter.
For example, let's say we are editing a Java project and want to use the "java" linter, which requires a source path and class path. In addition, we want to ignore serialization errors. Our project settings might look like this:
{
"folders":
[
{
"path": "/Users/aparajita/Projects/foo/src"
}
],
"settings":
{
"SublimeLinter":
{
"Java":
{
"working_directory": "/Users/aparajita/Projects/foo",
"lint_args":
[
"-sourcepath", "src",
"-classpath", "libs/log4j-1.2.9.jar:libs/commons-logging-1.1.jar",
"-Xlint", "-Xlint:-serial",
"{filename}"
]
}
}
}
}
The jshint follows convention set by node-jshint (though node is not required) and will attempt to locate the configuration file for you starting in pwd. (or "present working directory") If this does not yield a .jshintrc file, it will move one level up (..) the directory tree all the way up to the filesystem root. If a file is found, it stops immediately and uses that set of configuration instead of "jshint_options".
### Customizing colors
**IMPORTANT** - The theme style names have recently changed. The old and new color
names are:
Old New
--------------------- -----------------------------
sublimelinter.<type> sublimelinter.outline.<type>
invalid.<type> sublimelinter.underline.<type>
Please change the names in your color themes accordingly.
There are three types of "errors" flagged by SublimeLinter: illegal,
violation, and warning. For each type, SublimeLinter will indicate the offending
line and the character position at which the error occurred on the line.
By default SublimeLinter will outline offending lines using the background color
of the "sublimelinter.outline.<type>" theme style, and underline the character position
using the background color of the "sublimelinter.underline.<type>" theme style, where <type>
is one of the three error types.
If these styles are not defined, the color will be black when there is a light
background color and black when there is a dark background color. You may
define a single "sublimelinter.outline" or "sublimelinter.underline" style to color all three types,
or define separate substyles for one or more types to color them differently.
If you want to make the offending lines glaringly obvious (perhaps for those
who tend to ignore lint errors), you can set the user setting:
"sublimelinter_mark_style": "fill"
When this is set true, lines that have errors will be colored with the background
and foreground color of the "sublime.outline.<type>" theme style. Unless you have defined
those styles, this setting should be left as "outline".
You may want to disable drawing of outline boxes entirely. If so, change
using the user setting to:
"sublimelinter_mark_style": "none"
You may also mark lines with errors by putting an "x" in the gutter with the user setting:
"sublimelinter_gutter_marks": true
To customize the colors used for highlighting errors and user notes, add the following
to your theme (adapting the color to your liking):
<dict>
<key>name</key>
<string>SublimeLinter Annotations</string>
<key>scope</key>
<string>sublimelinter.annotations</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#FFFFAA</string>
<key>foreground</key>
<string>#FFFFFF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>SublimeLinter Error Outline</string>
<key>scope</key>
<string>sublimelinter.outline.illegal</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#FF4A52</string>
<key>foreground</key>
<string>#FFFFFF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>SublimeLinter Error Underline</string>
<key>scope</key>
<string>sublimelinter.underline.illegal</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#FF0000</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>SublimeLinter Warning Outline</string>
<key>scope</key>
<string>sublimelinter.outline.warning</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#DF9400</string>
<key>foreground</key>
<string>#FFFFFF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>SublimeLinter Warning Underline</string>
<key>scope</key>
<string>sublimelinter.underline.warning</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#FF0000</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>SublimeLinter Violation Outline</string>
<key>scope</key>
<string>sublimelinter.outline.violation</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#ffffff33</string>
<key>foreground</key>
<string>#FFFFFF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>SublimeLinter Violation Underline</string>
<key>scope</key>
<string>sublimelinter.underline.violation</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#FF0000</string>
</dict>
</dict>
Using
-----
SublimeLinter runs in one of three modes, which is determined by the "sublimelinter" user setting:
* **Background mode (the default)** - When the "sublimelinter" setting is true, linting is performed in the background as you modify a file (if the relevant linter supports it). If you like instant feedback, this is the best way to use SublimeLinter. If you want feedback, but not instantly, you can try another mode or set a minimum queue delay with the "sublimelinter_delay" setting, so that the linter will only run after a certain amount of idle time.
* **Load-save mode** - When the "sublimelinter" setting is "load-save", linting is performed only when a file is loaded and after saving. Errors are cleared as soon as the file is modified.
* **Save-only mode** - When the "sublimelinter" setting is "save-only", linting is performed only after a file is saved. Errors are cleared as soon as the file is modified.
* **On demand mode** - When the "sublimelinter" setting is false, linting is performed only when initiated by you. Use the `Control+Command+L` (OS X) or `Control+Alt+L` (Linux/Windows) key equivalent or the Command Palette to lint the current file. If the current file has no associated linter, the command will not be available.
Within a file whose language/syntax is supported by SublimeLinter, you can control SublimeLinter via the Command Palette (`Command+Shift+P` on OS X, `Control+Shift+P` on Linux/Windows). The available commands are:
* **SublimeLinter: Lint Current File** - Lints the current file, highlights any errors and displays how many errors were found.
* **SublimeLinter: Show Error List** - Lints the current file, highlights any errors and displays a quick panel with any errors that are found. Selecting an item from the quick panel jumps to that line.
* **SublimeLinter: Background Linting** - Enables background linting mode for the current view and lints it.
* **SublimeLinter: Disable Linting** - Disables linting mode for the current view and clears all lint errors.
* **SublimeLinter: Load-Save Linting** - Enables load-save linting mode for the current view and clears all lint errors.
* **SublimeLinter: Save-Only Linting** - Enables save-only linting mode for the current view and clears all lint errors.
* **SublimeLinter: Reset** - Clears all lint errors and sets the linting mode to the value in the SublimeLinter.sublime-settings file.
Depending on the file and the current state of background enabling, some of the commands will not be available.
When an error is highlighted by the linter, putting the cursor on the offending line will result in the error message being displayed on the status bar.
If you want to be shown a popup list of all errors whenever a file is saved, modify the user setting:
"sublimelinter_popup_errors_on_save": true
If there are errors in the file, a quick panel will appear which shows the error message, line number and source code for each error. The starting location of all errors on the line are marked with "^". Selecting an error in the quick panel jumps directly to the location of the first error on that line.
While editing a file, you can quickly move to the next/previous lint error with the following key equivalents:
* **OS X**:
next: Control+Command+E
prev: Control+Command+Shift+E
* **Linux, Windows**:
next: Control+Alt+E
prev: Control+Alt+Shift+E
By default the search will wrap. You can turn wrapping off with the user setting:
"sublimelinter_wrap_find": false
Please note: these key commands may conflict with other important cmds (such as generating the € character - this was discussed in issue [#182](https://github.com/SublimeLinter/SublimeLinter/issues/182)). If these controls are problematic, you may always adjust your settings by copying the defaults stored in `Preferences->Package Settings->SublimeLinter->Key Bindings - Default` into `Preferences->Key Bindings - User` and then modifying the values appropriately.
Troubleshooting
---------------
If a linter does not seem to be working, you can check the ST2 console to see if it was enabled. When SublimeLinter is loaded, you will see messages in the console like this:
Reloading plugin /Users/aparajita/Library/Application Support/Sublime Text 2/Packages/SublimeLinter/sublimelinter_plugin.py
SublimeLinter: JavaScript loaded
SublimeLinter: annotations loaded
SublimeLinter: Objective-J loaded
SublimeLinter: perl loaded
SublimeLinter: php loaded
SublimeLinter: python loaded
SublimeLinter: ruby loaded
SublimeLinter: pylint loaded
The first time a linter is asked to lint, it will check to see if it can be enabled. You will then see messages like this:
SublimeLinter: JavaScript enabled (using JavaScriptCore)
SublimeLinter: Ruby enabled (using "ruby" for executable)
Let's say the ruby linter is not working. If you look at the console, you may see a message like this:
SublimeLinter: ruby disabled ("ruby" cannot be found)
This means that the ruby executable cannot be found on your system, which means it is not installed or not in your executable path.
Creating New Linters
--------------------
If you wish to create a new linter to support a new language, SublimeLinter makes it easy. Here are the steps involved:
* Create a new file in sublimelinter/modules. If your linter uses an external executable, you will probably want to copy perl.py. If your linter uses built in code, copy objective-j.py. The convention is to name the file the same as the language that will be linted.
* Configure the CONFIG dict in your module. See the comments in base\_linter.py for information on the values in that dict. You only need to set the values in your module that differ from the defaults in base\_linter.py, as your module's CONFIG is merged with the default. Note that if your linter uses an external executable that does not take stdin, setting 'input\_method' to INPUT\_METHOD\_TEMP\_FILE will allow interactive linting with that executable.
* If your linter uses built in code, override `built_in_check()` and return the errors found.
* Override `parse_errors()` and process the errors. If your linter overrides `built_in_check()`, `parse_errors()` will receive the result of that method. If your linter uses an external executable, `parse_errors()` receives the raw output of the executable, stripped of leading and trailing whitespace.
* If you linter is powered via Javascript (eg. Node.js), there are few steps that will simplify the integration.
Create a folder matching your linter name in the `SublimeLinter/sublimelinter/modules/lib` directory. This folder should include the linting library JS file (eg. jshint.js, csslint-Node.js) and a **linter.js** file. The **linter.js** file should `require()` the actual linter library file and export a `lint()` function. The `lint()` function should return a list of errors back to the python language handler file (via the `errors` parameter to the `parse_errors()` method).
Although **linter.js** should follow the Node.js api, the linter may also be run via JavaScriptCore on OS X if Node.js is not installed. In the case where JavaScriptCore is used, require + export are shimmed to keep things consistent. However, it is important not to assume that a full Node.js api is available. If you must know what JS engine you are using, you may check for `USING_JSC` to be set as `true` when JavaScriptCore is used.
For examples of using the JS engines, see **csslint**, **jslint**, and **jshint** in `SublimeLinter/sublimelinter/modules/libs` and the respective python code of **css.py** and **javascript.py** in `SublimeLinter/sublimelinter/modules`.
If your linter has more complex requirements, see the comments for CONFIG in base\_linter.py, and use the existing linters as guides.
Beta
----
The SublimeLinter Beta package is available via Package Control. The beta version is considered unstable and should only be used for testing the next release. If you like living on the edge, please report any bugs you find on the [SublimeLinter issues](https://github.com/SublimeLinter/SublimeLinter/issues) page.

View File

@@ -0,0 +1,963 @@
# -*- coding: utf-8 -*-
from functools import partial
import os
import re
import sys
import time
import threading
import sublime
import sublime_plugin
from sublimelinter.loader import Loader
from sublimelinter.modules.base_linter import INPUT_METHOD_FILE
LINTERS = {} # mapping of language name to linter module
QUEUE = {} # views waiting to be processed by linter
ERRORS = {} # error messages on given line obtained from linter; they are
# displayed in the status bar when cursor is on line with error
VIOLATIONS = {} # violation messages, they are displayed in the status bar
WARNINGS = {} # warning messages, they are displayed in the status bar
UNDERLINES = {} # underline regions related to each lint message
TIMES = {} # collects how long it took the linting to complete
MOD_LOAD = Loader(os.getcwdu(), LINTERS) # utility to load (and reload
# if necessary) linter modules [useful when working on plugin]
# For snappier linting, different delays are used for different linting times:
# (linting time, delays)
DELAYS = (
(50, (50, 100)),
(100, (100, 300)),
(200, (200, 500)),
(400, (400, 1000)),
(800, (800, 2000)),
(1600, (1600, 3000)),
)
MARKS = {
'violation': ('', 'dot'),
'warning': ('', 'dot'),
'illegal': ('', 'circle'),
}
# All available settings for SublimeLinter;
# only these are inherited from SublimeLinter.sublime-settings
ALL_SETTINGS = [
'annotations',
'csslint_options',
'gjslint_ignore',
'gjslint_options',
'javascript_linter',
'jshint_options',
'jslint_options',
'pep8',
'pep8_ignore',
'perl_linter',
'pyflakes_ignore',
'pyflakes_ignore_import_*',
'coffeelint_options',
'sublimelinter',
'sublimelinter_delay',
'sublimelinter_disable',
'sublimelinter_executable_map',
'sublimelinter_fill_outlines',
'sublimelinter_gutter_marks',
'sublimelinter_mark_style',
'sublimelinter_notes',
'sublimelinter_objj_check_ascii',
'sublimelinter_popup_errors_on_save',
'sublimelinter_syntax_map',
'sublimelinter_wrap_find',
]
WHITESPACE_RE = re.compile(r'\s+')
def get_delay(t, view):
delay = 0
for _t, d in DELAYS:
if _t <= t:
delay = d
delay = delay or DELAYS[0][1]
# If the user specifies a delay greater than the built in delay,
# figure they only want to see marks when idle.
minDelay = int(view.settings().get('sublimelinter_delay', 0) * 1000)
if minDelay > delay[1]:
erase_lint_marks(view)
return (minDelay, minDelay) if minDelay > delay[1] else delay
def last_selected_lineno(view):
viewSel = view.sel()
if not viewSel:
return None
return view.rowcol(viewSel[0].end())[0]
def update_statusbar(view):
vid = view.id()
lineno = last_selected_lineno(view)
errors = []
if lineno is not None:
if vid in ERRORS and lineno in ERRORS[vid]:
errors.extend(ERRORS[vid][lineno])
if vid in VIOLATIONS and lineno in VIOLATIONS[vid]:
errors.extend(VIOLATIONS[vid][lineno])
if vid in WARNINGS and lineno in WARNINGS[vid]:
errors.extend(WARNINGS[vid][lineno])
if errors:
view.set_status('Linter', '; '.join(errors))
else:
view.erase_status('Linter')
def run_once(linter, view, **kwargs):
'''run a linter on a given view regardless of user setting'''
if not linter:
return
vid = view.id()
ERRORS[vid] = {}
VIOLATIONS[vid] = {}
WARNINGS[vid] = {}
start = time.time()
text = view.substr(sublime.Region(0, view.size())).encode('utf-8')
lines, error_underlines, violation_underlines, warning_underlines, ERRORS[vid], VIOLATIONS[vid], WARNINGS[vid] = linter.run(view, text, view.file_name().encode('utf-8') or '')
UNDERLINES[vid] = error_underlines[:]
UNDERLINES[vid].extend(violation_underlines)
UNDERLINES[vid].extend(warning_underlines)
add_lint_marks(view, lines, error_underlines, violation_underlines, warning_underlines)
if view.settings().get('sublimelinter_notes'):
highlight_notes(view)
update_statusbar(view)
end = time.time()
TIMES[vid] = (end - start) * 1000 # Keep how long it took to lint
if kwargs.get('event', None) == 'on_post_save' and view.settings().get('sublimelinter_popup_errors_on_save'):
popup_error_list(view)
def popup_error_list(view):
vid = view.id()
errors = ERRORS[vid].copy()
for message_map in [VIOLATIONS[vid], WARNINGS[vid]]:
for line, messages in message_map.items():
if line in errors:
errors[line].extend(messages)
else:
errors[line] = messages
# Flatten the errors into a list
error_list = []
for line in sorted(errors.keys()):
for index, message in enumerate(errors[line]):
error_list.append({'line': line, 'message': message})
panel_items = []
for error in error_list:
line_text = view.substr(view.full_line(view.text_point(error['line'], 0)))
item = [error['message'], u'{0}: {1}'.format(error['line'] + 1, line_text.strip())]
panel_items.append(item)
def on_done(selected_item):
if selected_item == -1:
return
selected = view.sel()
selected.clear()
error = error_list[selected_item]
region_begin = view.text_point(error['line'], 0)
# Go to the first non-whitespace character of the line
line_text = view.substr(view.full_line(region_begin))
match = WHITESPACE_RE.match(line_text)
if (match):
region_begin += len(match.group(0))
selected.add(sublime.Region(region_begin, region_begin))
# We have to force a move to update the cursor position
view.run_command('move', {'by': 'characters', 'forward': True})
view.run_command('move', {'by': 'characters', 'forward': False})
view.show_at_center(region_begin)
view.window().show_quick_panel(panel_items, on_done)
def add_lint_marks(view, lines, error_underlines, violation_underlines, warning_underlines):
'''Adds lint marks to view.'''
vid = view.id()
erase_lint_marks(view)
types = {'warning': warning_underlines, 'violation': violation_underlines, 'illegal': error_underlines}
for type_name, underlines in types.items():
if underlines:
view.add_regions('lint-underline-' + type_name, underlines, 'sublimelinter.underline.' + type_name, sublime.DRAW_EMPTY_AS_OVERWRITE)
if lines:
outline_style = view.settings().get('sublimelinter_mark_style', 'outline')
# This test is for the legacy "fill" setting; it will be removed
# in a future version (likely v1.7).
if view.settings().get('sublimelinter_fill_outlines', False):
outline_style = 'fill'
gutter_mark_enabled = True if view.settings().get('sublimelinter_gutter_marks', False) else False
outlines = {'warning': [], 'violation': [], 'illegal': []}
for line in ERRORS[vid]:
outlines['illegal'].append(view.full_line(view.text_point(line, 0)))
for line in WARNINGS[vid]:
outlines['warning'].append(view.full_line(view.text_point(line, 0)))
for line in VIOLATIONS[vid]:
outlines['violation'].append(view.full_line(view.text_point(line, 0)))
for lint_type in outlines:
if outlines[lint_type]:
args = [
'lint-outlines-{0}'.format(lint_type),
outlines[lint_type],
'sublimelinter.outline.{0}'.format(lint_type),
MARKS[lint_type][gutter_mark_enabled]
]
if outline_style == 'none':
args.append(sublime.HIDDEN)
elif outline_style == 'fill':
pass # outlines are filled by default
else:
args.append(sublime.DRAW_OUTLINED)
view.add_regions(*args)
def erase_lint_marks(view):
'''erase all "lint" error marks from view'''
view.erase_regions('lint-underline-illegal')
view.erase_regions('lint-underline-violation')
view.erase_regions('lint-underline-warning')
view.erase_regions('lint-outlines-illegal')
view.erase_regions('lint-outlines-violation')
view.erase_regions('lint-outlines-warning')
view.erase_regions('lint-annotations')
def get_lint_regions(view, reverse=False, coalesce=False):
vid = view.id()
underlines = UNDERLINES.get(vid, [])[:]
if (coalesce):
# Each of these regions is one character, so transform it into the character points
points = sorted([region.begin() for region in underlines])
# Now coalesce adjacent characters into a single region
underlines = []
last_point = -999
for point in points:
if point != last_point + 1:
underlines.append(sublime.Region(point, point))
else:
region = underlines[-1]
underlines[-1] = sublime.Region(region.begin(), point)
last_point = point
# Now get all outlines, which includes the entire line where underlines are
outlines = view.get_regions('lint-outlines-illegal')
outlines.extend(view.get_regions('lint-outlines-violation'))
outlines.extend(view.get_regions('lint-outlines-warning'))
outlines.extend(view.get_regions('lint-annotations'))
# If an outline region contains an underline region, use only the underline
regions = underlines
for outline in outlines:
contains_underlines = False
for underline in underlines:
if outline.contains(underline):
contains_underlines = True
break
if not contains_underlines:
regions.append(outline)
return sorted(regions, key=lambda x: x.begin(), reverse=reverse)
def select_lint_region(view, region):
selected = view.sel()
selected.clear()
# Find the first underline region within the region to select.
# If there are none, put the cursor at the beginning of the line.
underlineRegion = find_underline_within(view, region)
if underlineRegion is None:
underlineRegion = sublime.Region(region.begin(), region.begin())
selected.add(underlineRegion)
view.show(underlineRegion, True)
def find_underline_within(view, region):
underlines = view.get_regions('lint-underline-illegal')
underlines.extend(view.get_regions('lint-underline-violation'))
underlines.extend(view.get_regions('lint-underline-warning'))
underlines.sort(key=lambda x: x.begin())
for underline in underlines:
if region.contains(underline):
return underline
return None
def syntax_name(view):
syntax = os.path.basename(view.settings().get('syntax'))
syntax = os.path.splitext(syntax)[0]
return syntax
def select_linter(view, ignore_disabled=False):
'''selects the appropriate linter to use based on language in current view'''
syntax = syntax_name(view)
lc_syntax = syntax.lower()
language = None
linter = None
syntaxMap = view.settings().get('sublimelinter_syntax_map', {})
if syntax in syntaxMap:
language = syntaxMap[syntax].lower()
elif lc_syntax in syntaxMap:
language = syntaxMap[lc_syntax].lower()
elif lc_syntax in LINTERS:
language = lc_syntax
if language:
if ignore_disabled:
disabled = []
else:
disabled = view.settings().get('sublimelinter_disable', [])
if language not in disabled:
linter = LINTERS[language]
# If the enabled state is False, it must be checked.
# Enabled checking has to be deferred to first view use because
# user settings cannot be loaded during plugin startup.
if not linter.enabled:
enabled, message = linter.check_enabled(view)
print 'SublimeLinter: {0} {1} ({2})'.format(language, 'enabled' if enabled else 'disabled', message)
if not enabled:
del LINTERS[language]
linter = None
return linter
def highlight_notes(view):
'''highlight user-specified annotations in a file'''
view.erase_regions('lint-annotations')
text = view.substr(sublime.Region(0, view.size()))
regions = LINTERS['annotations'].built_in_check(view, text, '')
if regions:
view.add_regions('lint-annotations', regions, 'sublimelinter.annotations', sublime.DRAW_EMPTY_AS_OVERWRITE)
def _update_view(view, filename, **kwargs):
# It is possible that by the time the queue is run,
# the original file is no longer being displayed in the view,
# or the view may be gone. This happens especially when
# viewing files temporarily by single-clicking on a filename
# in the sidebar or when selecting a file through the choose file palette.
valid_view = False
view_id = view.id()
for window in sublime.windows():
for v in window.views():
if v.id() == view_id:
valid_view = True
break
if not valid_view or view.is_loading() or view.file_name().encode('utf-8') != filename:
return
try:
run_once(select_linter(view), view, **kwargs)
except RuntimeError, ex:
print ex
def queue_linter(linter, view, timeout=-1, preemptive=False, event=None):
'''Put the current view in a queue to be examined by a linter'''
if linter is None:
erase_lint_marks(view) # may have changed file type and left marks behind
# No point in queuing anything if no linters will run
if not view.settings().get('sublimelinter_notes'):
return
if preemptive:
timeout = busy_timeout = 0
elif timeout == -1:
timeout, busy_timeout = get_delay(TIMES.get(view.id(), 100), view)
else:
busy_timeout = timeout
kwargs = {'timeout': timeout, 'busy_timeout': busy_timeout, 'preemptive': preemptive, 'event': event}
queue(view, partial(_update_view, view, view.file_name().encode('utf-8'), **kwargs), kwargs)
def _callback(view, filename, kwargs):
kwargs['callback'](view, filename, **kwargs)
def background_linter():
__lock_.acquire()
try:
callbacks = QUEUE.values()
QUEUE.clear()
finally:
__lock_.release()
for callback in callbacks:
sublime.set_timeout(callback, 0)
################################################################################
# Queue dispatcher system:
queue_dispatcher = background_linter
queue_thread_name = 'background linter'
MAX_DELAY = 10
def queue_loop():
'''An infinite loop running the linter in a background thread meant to
update the view after user modifies it and then does no further
modifications for some time as to not slow down the UI with linting.'''
global __signaled_, __signaled_first_
while __loop_:
#print 'acquire...'
__semaphore_.acquire()
__signaled_first_ = 0
__signaled_ = 0
#print 'DISPATCHING!', len(QUEUE)
queue_dispatcher()
def queue(view, callback, kwargs):
global __signaled_, __signaled_first_
now = time.time()
__lock_.acquire()
try:
QUEUE[view.id()] = callback
timeout = kwargs['timeout']
busy_timeout = kwargs['busy_timeout']
if now < __signaled_ + timeout * 4:
timeout = busy_timeout or timeout
__signaled_ = now
_delay_queue(timeout, kwargs['preemptive'])
if not __signaled_first_:
__signaled_first_ = __signaled_
#print 'first',
#print 'queued in', (__signaled_ - now)
finally:
__lock_.release()
def _delay_queue(timeout, preemptive):
global __signaled_, __queued_
now = time.time()
if not preemptive and now <= __queued_ + 0.01:
return # never delay queues too fast (except preemptively)
__queued_ = now
_timeout = float(timeout) / 1000
if __signaled_first_:
if MAX_DELAY > 0 and now - __signaled_first_ + _timeout > MAX_DELAY:
_timeout -= now - __signaled_first_
if _timeout < 0:
_timeout = 0
timeout = int(round(_timeout * 1000, 0))
new__signaled_ = now + _timeout - 0.01
if __signaled_ >= now - 0.01 and (preemptive or new__signaled_ >= __signaled_ - 0.01):
__signaled_ = new__signaled_
#print 'delayed to', (preemptive, __signaled_ - now)
def _signal():
if time.time() < __signaled_:
return
__semaphore_.release()
sublime.set_timeout(_signal, timeout)
def delay_queue(timeout):
__lock_.acquire()
try:
_delay_queue(timeout, False)
finally:
__lock_.release()
# only start the thread once - otherwise the plugin will get laggy
# when saving it often.
__semaphore_ = threading.Semaphore(0)
__lock_ = threading.Lock()
__queued_ = 0
__signaled_ = 0
__signaled_first_ = 0
# First finalize old standing threads:
__loop_ = False
__pre_initialized_ = False
def queue_finalize(timeout=None):
global __pre_initialized_
for thread in threading.enumerate():
if thread.isAlive() and thread.name == queue_thread_name:
__pre_initialized_ = True
thread.__semaphore_.release()
thread.join(timeout)
queue_finalize()
# Initialize background thread:
__loop_ = True
__active_linter_thread = threading.Thread(target=queue_loop, name=queue_thread_name)
__active_linter_thread.__semaphore_ = __semaphore_
__active_linter_thread.start()
################################################################################
UNRECOGNIZED = '''
* Unrecognized option * : %s
==============================================
'''
def view_in_tab(view, title, text, file_type):
'''Helper function to display information in a tab.
'''
tab = view.window().new_file()
tab.set_name(title)
_id = tab.buffer_id()
tab.set_scratch(_id)
tab.settings().set('gutter', True)
tab.settings().set('line_numbers', False)
tab.set_syntax_file(file_type)
ed = tab.begin_edit()
tab.insert(ed, 0, text)
tab.end_edit(ed)
return tab, _id
def lint_views(linter):
if not linter:
return
viewsToLint = []
for window in sublime.windows():
for view in window.views():
viewLinter = select_linter(view)
if viewLinter == linter:
viewsToLint.append(view)
for view in viewsToLint:
queue_linter(linter, view, preemptive=True)
def reload_view_module(view):
for name, linter in LINTERS.items():
module = sys.modules[linter.__module__]
if module.__file__.encode('utf-8') == view.file_name().encode('utf-8'):
print 'SublimeLinter: reloading language:', linter.language
MOD_LOAD.reload_module(module)
lint_views(linter)
break
def settings_changed():
for window in sublime.windows():
for view in window.views():
linter = select_linter(view)
if (linter):
reload_settings(view)
def reload_settings(view):
'''Restores user settings.'''
settings = sublime.load_settings(__name__ + '.sublime-settings')
settings.clear_on_change(__name__)
settings.add_on_change(__name__, settings_changed)
for setting in ALL_SETTINGS:
if settings.get(setting) != None:
view.settings().set(setting, settings.get(setting))
if view.settings().get('sublimelinter') == None:
view.settings().set('sublimelinter', True)
class LintCommand(sublime_plugin.TextCommand):
'''command to interact with linters'''
def __init__(self, view):
self.view = view
self.help_called = False
def run_(self, action):
'''method called by default via view.run_command;
used to dispatch to appropriate method'''
if not action:
return
try:
lc_action = action.lower()
except AttributeError:
return
if lc_action == 'reset':
self.reset()
elif lc_action == 'on':
self.on()
elif lc_action == 'load-save':
self.enable_load_save()
elif lc_action == 'save-only':
self.enable_save_only()
elif lc_action == 'off':
self.off()
elif action.lower() in LINTERS:
self._run(lc_action)
def reset(self):
'''Removes existing lint marks and restores user settings.'''
erase_lint_marks(self.view)
reload_settings(self.view)
def on(self):
'''Turns background linting on.'''
self.view.settings().set('sublimelinter', True)
queue_linter(select_linter(self.view), self.view, preemptive=True)
def enable_load_save(self):
'''Turns load-save linting on.'''
self.view.settings().set('sublimelinter', 'load-save')
erase_lint_marks(self.view)
def enable_save_only(self):
'''Turns save-only linting on.'''
self.view.settings().set('sublimelinter', 'save-only')
erase_lint_marks(self.view)
def off(self):
'''Turns background linting off.'''
self.view.settings().set('sublimelinter', False)
erase_lint_marks(self.view)
def _run(self, name):
'''runs an existing linter'''
run_once(LINTERS[name.lower()], self.view)
class BackgroundLinter(sublime_plugin.EventListener):
'''This plugin controls a linter meant to work in the background
to provide interactive feedback as a file is edited. It can be
turned off via a setting.
'''
def __init__(self):
super(BackgroundLinter, self).__init__()
self.lastSelectedLineNo = -1
def on_modified(self, view):
if view.is_scratch():
return
if view.settings().get('sublimelinter') != True:
erase_lint_marks(view)
return
linter = select_linter(view)
# File-based linters are not invoked during a modify
if linter and linter.input_method == INPUT_METHOD_FILE:
erase_lint_marks(view)
return
# Reset the last selected line number so that the current line will show error messages
# when update_statusbar is called.
self.lastSelectedLineNo = -1
queue_linter(linter, view)
def on_load(self, view):
reload_settings(view)
if view.is_scratch() or view.settings().get('sublimelinter') == False or view.settings().get('sublimelinter') == 'save-only':
return
queue_linter(select_linter(view), view, event='on_load')
def on_post_save(self, view):
if view.is_scratch() or view.settings().get('sublimelinter') == False:
return
reload_view_module(view)
queue_linter(select_linter(view), view, preemptive=True, event='on_post_save')
def on_selection_modified(self, view):
if view.is_scratch():
return
delay_queue(1000) # on movement, delay queue (to make movement responsive)
# We only display errors in the status bar for the last line in the current selection.
# If that line number has not changed, there is no point in updating the status bar.
lastSelectedLineNo = last_selected_lineno(view)
if lastSelectedLineNo != self.lastSelectedLineNo:
self.lastSelectedLineNo = lastSelectedLineNo
update_statusbar(view)
class FindLintErrorCommand(sublime_plugin.TextCommand):
'''This command is just a superclass for other commands, it is never enabled.'''
def is_enabled(self):
return select_linter(self.view) is not None
def find_lint_error(self, forward):
linter = select_linter(self.view, ignore_disabled=True)
if not linter:
return
self.view.run_command('lint', linter.language)
regions = get_lint_regions(self.view, reverse=not forward, coalesce=True)
if len(regions) == 0:
sublime.error_message('No lint errors.')
return
selected = self.view.sel()
point = selected[0].begin() if forward else selected[-1].end()
regionToSelect = None
# If going forward, find the first region beginning after the point.
# If going backward, find the first region ending before the point.
# If nothing is found in the given direction, wrap to the first/last region.
if forward:
for index, region in enumerate(regions):
if point < region.begin():
regionToSelect = region
break
else:
for index, region in enumerate(regions):
if point > region.end():
regionToSelect = region
break
# If there is only one error line and the cursor is in that line, we cannot move.
# Otherwise wrap to the first/last error line unless settings disallow that.
if regionToSelect is None and (len(regions) > 1 or not regions[0].contains(point)):
if self.view.settings().get('sublimelinter_wrap_find', True):
regionToSelect = regions[0]
if regionToSelect is not None:
select_lint_region(self.view, regionToSelect)
else:
sublime.error_message('No {0} lint errors.'.format('next' if forward else 'previous'))
return regionToSelect
class FindNextLintErrorCommand(FindLintErrorCommand):
def run(self, edit):
'''
Move the cursor to the next lint error in the current view.
The search will wrap to the top unless the sublimelinter_wrap_find
setting is set to false.
'''
self.find_lint_error(forward=True)
class FindPreviousLintErrorCommand(FindLintErrorCommand):
def run(self, edit):
'''
Move the cursor to the previous lint error in the current view.
The search will wrap to the bottom unless the sublimelinter_wrap_find
setting is set to false.
'''
self.find_lint_error(forward=False)
class SublimelinterWindowCommand(sublime_plugin.WindowCommand):
def is_enabled(self):
view = self.window.active_view()
if view:
if view.is_scratch():
return False
else:
return True
else:
return False
def run_(self, args):
pass
class SublimelinterAnnotationsCommand(SublimelinterWindowCommand):
'''Commands to extract annotations and display them in
a file
'''
def run_(self, args):
linter = LINTERS.get('annotations', None)
if linter is None:
return
view = self.window.active_view()
if not view:
return
text = view.substr(sublime.Region(0, view.size())).encode('utf-8')
filename = view.file_name().encode('utf-8')
notes = linter.extract_annotations(text, view, filename)
_, filename = os.path.split(filename)
annotations_view, _id = view_in_tab(view, 'Annotations from {0}'.format(filename), notes, '')
class SublimelinterCommand(SublimelinterWindowCommand):
def is_enabled(self):
enabled = super(SublimelinterCommand, self).is_enabled()
if not enabled:
return False
linter = select_linter(self.window.active_view(), ignore_disabled=True)
return linter is not None
def run_(self, args={}):
view = self.window.active_view()
action = args.get('action', '')
if view and action:
if action == 'lint':
self.lint_view(view, show_popup_list=args.get('show_popup', False))
else:
view.run_command('lint', action)
def lint_view(self, view, show_popup_list):
linter = select_linter(view, ignore_disabled=True)
if linter:
view.run_command('lint', linter.language)
regions = get_lint_regions(view, coalesce=True)
if regions:
if show_popup_list:
popup_error_list(view)
else:
sublime.error_message('{0} lint error{1}.'.format(len(regions), 's' if len(regions) != 1 else ''))
else:
sublime.error_message('No lint errors.')
else:
syntax = syntax_name(view)
sublime.error_message('No linter for the syntax "{0}"'.format(syntax))
class SublimelinterLintCommand(SublimelinterCommand):
def is_enabled(self):
enabled = super(SublimelinterLintCommand, self).is_enabled()
if enabled:
view = self.window.active_view()
if view and view.settings().get('sublimelinter') == True:
return False
return enabled
class SublimelinterShowErrorsCommand(SublimelinterCommand):
def is_enabled(self):
return super(SublimelinterShowErrorsCommand, self).is_enabled()
class SublimelinterEnableLoadSaveCommand(SublimelinterCommand):
def is_enabled(self):
enabled = super(SublimelinterEnableLoadSaveCommand, self).is_enabled()
if enabled:
view = self.window.active_view()
if view and view.settings().get('sublimelinter') == 'load-save':
return False
return enabled
class SublimelinterEnableSaveOnlyCommand(SublimelinterCommand):
def is_enabled(self):
enabled = super(SublimelinterEnableSaveOnlyCommand, self).is_enabled()
if enabled:
view = self.window.active_view()
if view and view.settings().get('sublimelinter') == 'save-only':
return False
return enabled
class SublimelinterDisableCommand(SublimelinterCommand):
def is_enabled(self):
enabled = super(SublimelinterDisableCommand, self).is_enabled()
if enabled:
view = self.window.active_view()
if view and view.settings().get('sublimelinter') == False:
return False
return enabled

View File

@@ -0,0 +1,293 @@
/*
SublimeLinter default settings
*/
{
/*
Sets the mode in which SublimeLinter runs:
true - Linting occurs in the background as you type (the default).
false - Linting only occurs when you initiate it.
"load-save" - Linting occurs only when a file is loaded and saved.
"save-only" - Linting occurs only when a file is saved.
*/
"sublimelinter": true,
/*
Maps language names **as listed at the beginning of the README** (but all lowercase)
to executables for non-built in linters. If the executable is not in the default system path,
or on posix systems is not in /usr/local/bin or ~/bin, then you must specify
the full path to the executable. Note that paths in Windows must use double
backslashes, for example "C:\\Program Files (x86)\\nodejs\\node.exe".
Please note that the map _keys_ do not always match the name of the
executable, but rather the language syntax for the executable to lint.
This is the effective default map; your mappings may override these.
"sublimelinter_executable_map":
{
"perl": "perl",
"php": "php",
"ruby": "ruby"
},
*/
"sublimelinter_executable_map":
{
},
/*
Maps syntax names to linters. This allows variations on a syntax
(for example "Python (Django)") to be linted. The key is
the name of the syntax **as it appears in the syntax list
at the bottom right of the window**, and the value
is the linter name **as listed in the README** (all lowercase)
that the syntax maps to.
*/
"sublimelinter_syntax_map":
{
"Python Django": "python",
"Ruby on Rails": "ruby",
"C++": "c"
},
// An array of linter names to disable. Names should be lowercase.
"sublimelinter_disable":
[
],
/*
The minimum delay in seconds (fractional seconds are okay) before
a linter is run when the "sublimelinter" setting is true. This allows
you to have background linting active, but defer the actual linting
until you are idle. When this value is greater than the built in linting delay,
errors are erased when the file is modified, since the assumption is
you don't want to see errors while you type.
*/
"sublimelinter_delay": 2,
/*
Selects the way the lines with errors or warnings are marked; "outline"
(default) draws outline boxes around the lines, "fill" fills the lines
with the outline color, and "none" disables all outline styles
(useful if "sublimelinter_gutter_marks" is set).
*/
"sublimelinter_mark_style": "outline",
/*
If true, lines with errors or warnings will be filled in with the
outline color.
This setting is DEPRECATED and will be ignored in future
versions. Use "sublimelinter_mark_style" instead. For backwards
compatibility reasons, this setting overrides "sublimelinter_mark_style"
if that one is set to "outline", but has no effect if it's set to "none".
*/
"sublimelinter_fill_outlines": false,
// If true, lines with errors or warnings will have a gutter mark.
"sublimelinter_gutter_marks": false,
// If true, the find next/previous error commands will wrap.
"sublimelinter_wrap_find": true,
// If true, when the file is saved any errors will appear in a popup list
"sublimelinter_popup_errors_on_save": false,
// Javascript linter: "gjslint" to use the closure javascript linter (if available),
// or either "jshint" or "jslint" to use a built in linter.
"javascript_linter": "jshint",
// jshint: options for linting JavaScript. See http://www.jshint.com/options/ for more info.
// By deault, eval is allowed.
"jshint_options":
{
// To fix column positions for JSHint errors you may want to add `"indent": 1` to your
// **User** "jshint_options". This issue affects users with tabs for indentation.
// This fix was reverted due to a conflict with using the `"white": true` option.
// "indent": 1,
"evil": true,
"regexdash": true,
"browser": true,
"wsh": true,
"trailing": true,
"sub": true
},
// A list of command line options to send to gjslint. --nobeep is always sent.
"gjslint_options":
[
],
// A list of gjslint error numbers to ignore. The list of error codes is here:
// http://closure-linter.googlecode.com/svn/trunk/closure_linter/errors.py
"gjslint_ignore":
[
110 // line too long
],
// CSSLint options:
// Each rule can have three values: error|warning|true|false
// false => rule is disabled.
// true => alias to 'error'
// All rules are enabled by default.
// Currently the only difference between warnings and errors is in the prefix of the message in the Sublime status bar.
"csslint_options":
{
"adjoining-classes": "warning",
"box-model": true,
"box-sizing": "warning",
"compatible-vendor-prefixes": "warning",
"display-property-grouping": true,
"duplicate-background-images": "warning",
"duplicate-properties": true,
"empty-rules": true,
"errors": true,
"fallback-colors": "warning",
"floats": "warning",
"font-faces": "warning",
"font-sizes": "warning",
"gradients": "warning",
"ids": "warning",
"import": "warning",
"important": "warning",
"known-properties": true,
"outline-none": "warning",
"overqualified-elements": "warning",
"qualified-headings": "warning",
"regex-selectors": "warning",
"rules-count": "warning",
"shorthand": "warning",
"star-property-hack": "warning",
"text-indent": "warning",
"underscore-property-hack": "warning",
"unique-headings": "warning",
"universal-selector": "warning",
"vendor-prefix": true,
"zero-units": "warning"
},
// Set this to false to turn pep8 checking off completely
"pep8": true,
/*
A list of pep8 error numbers to ignore. By default "line too long" errors are ignored.
The list of error codes is in this file: https://github.com/jcrocholl/pep8/blob/master/pep8.py.
Search for "Ennn:", where nnn is a 3-digit number.
*/
"pep8_ignore":
[
"E501"
],
/*
If you use SublimeLinter for pyflakes checks, you can ignore some of the "undefined name xxx"
errors (comes in handy if you work with post-processors, globals/builtins available only at runtime, etc.).
You can control what names will be ignored with the user setting "pyflakes_ignore".
Example:
"pyflakes_ignore":
[
"some_custom_builtin_o_mine",
"A_GLOBAL_CONSTANT"
],
*/
"pyflakes_ignore":
[
],
/*
Ordinarily pyflakes will issue a warning when 'from foo import *' is used,
but it is ignored since the warning is not that helpful. If you want to see this warning,
set this option to false.
*/
"pyflakes_ignore_import_*": true,
/*
By default, CoffeeLint will help ensure you are writing idiomatic
CoffeeScript, but every rule is optional and configurable so it can be
tuned to fit your preferred coding style. To override any of CoffeeLint's
default options, start with this example configuration file and tweak as
needed. To enable an option, set its level to error and to disable an
option, set its level to ignore. If you set the level to warn, violations
will still be reported.
see: http://www.coffeelint.org/#options
CoffeeLint must be installed for these to be used, otherwise Sublime
Linter will revert to only checking for errors using the coffee
compiler
NOTE: if coffeelint_options['indentation']['value'] is not specified,
Sublime Linter will automatically use the number of spaces set for the
current file (or 1 if tabs are being used). This can be very useful
while switching between differently formatted projects.
Similarly, if coffeelint_options['no_tabs']['level'] is not set, weather or not
"Indent Using Spaces" (at the bottom-right corner of the window) is
checked will determine if tabs will be considered an error. If it's
checked, then tabs are an error, if it isn't, then we assume you like
tabs.
By default, neither of these are set.
*/
"coffeelint_options": {
"no_tabs": {
//"level": "error"
},
"indentation": {
//"value": 2,
"level": "error"
},
"no_trailing_whitespace": {
"level": "error"
},
"max_line_length": {
"value": 80,
"level": "error"
},
"camel_case_classes": {
"level": "error"
},
"no_implicit_braces": {
"level": "ignore"
},
"no_trailing_semicolons": {
"level": "error"
},
"no_plusplus": {
"level": "ignore"
},
"no_throwing_strings": {
"level": "error"
},
"cyclomatic_complexity": {
"value": 11,
"level": "ignore"
},
"line_endings": {
"value": "unix",
"level": "ignore"
},
"no_implicit_parens": {
"level": "ignore"
}
},
/*
Perl linter: "perl" to use the Perl language syntax check, or "perlcritic" to use Perl::Critic linting.
Perl is now set to use "perlcritic" by default due to a vulnerability with blindly running `perl -c`
on files with `BEGIN` or `CHECK` blocks.
*/
"perl_linter": "perlcritic",
// Objective-J: if true, non-ascii characters are flagged as an error.
"sublimelinter_objj_check_ascii": false,
// Set to true to highlight annotations
"sublimelinter_notes": false,
// The set of annotation phrases to highlight
"annotations": ["TODO", "README", "FIXME"]
}

View File

@@ -0,0 +1,419 @@
SublimeLinter 1.5.1 changelog
=============================
NEW FEATURES
- SublimeLinter keeps its settings in its own settings file now:
SublimeLinter.sublime-settings. You will need to copy your
user settings to this file. To do so, follow these steps:
1. Select "Preferences->Settings - User" in one tab/window.
The title of this tab should be "Preferences.sublime-settings".
2. Open another tab/window and select "Preferences->Package Settings->
SublimeLinter->Settings - User". The title of this window should be
"SublimeLinter.sublime-settings".
3. Copy/cut any of the following settings from Preferences.sublime-settings
to SublimeLinter.sublime-settings:
sublimelinter
sublimelinter_executable_map
sublimelinter_syntax_map
sublimelinter_disable
sublimelinter_delay
sublimelinter_fill_outlines
sublimelinter_gutter_marks
sublimelinter_wrap_find
sublimelinter_popup_errors_on_save
javascript_linter
jshint_options
pep8_ignore
pyflakes_ignore
pyflakes_ignore_import_*
sublimelinter_objj_check_ascii
4. Save SublimeLinter.sublime-settings. The changes may not take
effect until you restart Sublime Text.
When changes are made to the user SublimeLinter settings, they
are immediately reloaded into every open view. Note that this will
override any temporary changes you may have made to the settings in
a given view.
- The google closure Javascript linter (gjslint) is now supported
(https://developers.google.com/closure/utilities/docs/linter_howto).
There is a new setting, "javascript_linter", which determines which
linter to use, jshint or gjslinter. You may also customize gjslint
behavior with the "gjslint_options" and "gjslint_ignore" settings.
Please select "Preferences->Package Settings->SublimeLinter->Settings - Default"
for more information on these settings.
- The color theme names have been changed to avoid clashes with
built in names.
Old New
--------------------- -----------------------------
sublimelinter.<type> sublimelinter.outline.<type>
invalid.<type> sublimelinter.underline.<type>
You will have to update your color themes accordingly. Please select
"Preferences->Package Settings->SublimeLinter->README" and search
for "Customizing colors" for more information.
- When selecting an error from the popup error list, the view is centered
on the error line.
CHANGES/FIXES
- The PHP error regex has been updated to work with PHP 5.3.8 on Mac OS X.
- The popup error list will no longer choke on non-ASCII text.
- Selecting an error from the popup error list no longer attempts to go
directly to the point of an error as this could not be done reliably.
It will jump to the first non-whitespace character of the error's line.
- Go to next/previous error works correctly when an error line has no underlines.
- If an exception is thrown by jshint (e.g. too many errors), the errors
captured up to that point are displayed.
- The built in jshint has been updated from the master jshint.
- Fixed errors that would occur with the popup error list when there was
more than error on a line.
SublimeLinter 1.5.2 changelog
=============================
CHANGES/FIXES
- Fixed a problem with messages.json that prevented correct upgrading.
IMPORTANT
Please check to see if you have multiple listings for `SublimeLinter` in
`Preferences -> Package Settings`. If you do see 2 listings, please run
`Package Control: Upgrade/Overwrite All Packages` from the Command Palette
(`Tools -> Command Palette`).
SublimeLinter 1.5.3 changelog
=============================
CHANGES/FIXES
- Annotations have been fixed.
- Entries in "sublimelinter_syntax_map" take precedence over built in mappings.
- Lint errors in PHP files will hopefully not be logged to the PHP log file.
SublimeLinter 1.5.4 changelog
=============================
CHANGES/FIXES
- jshint.js has been updated to the latest master version.
- [issue #128] An "unsafe" option has been added to jshint. If set true,
any UTF-8 characters are allowed in the source.
SublimeLinter 1.5.5 changelog
=============================
CHANGES/FIXES
- This change log is available from the SublimeLinter preferences menu.
SublimeLinter 1.5.6 changelog
=============================
CHANGES/FIXES
- Fixed a problem with messages.json that prevented correct upgrading.
IMPORTANT
Please check to see if you have multiple listings for `SublimeLinter` in
`Preferences -> Package Settings`. If you do see 2 listings, please run
`Package Control: Upgrade/Overwrite All Packages` from the Command Palette
(`Tools -> Command Palette`).
SublimeLinter 1.5.7 changelog
=============================
CHANGES/FIXES
- node.js is the preferred Javascript engine on Mac OS X and will be used if it is installed.
JavaScriptCore does not handle non-ASCII text correctly and you should install node.js
if possible.
- If you imported BaseLinter.JSC_PATH, please change your linter to use the self.jsc_path()
method instead. JSC_PATH should no longer be considered public.
SublimeLinter 1.6.0 changelog
=============================
NEW FEATURES
- Simpler abstraction of Javascript engines for JS powered linters.
To leverage a JS linter, include a "linter.js" file; this file should
`require` the actual linter library file and export a `lint` function.
The `lint` function should return a list of errors back to the python
language handler file (via the `errors` parameter to the `parse_errors`
method).
Although "linter.js" should follow the Node.js api, the linter may also
be run via JavaScriptCore on OS X if Node.js is not installed. In the case
where JavaScriptCore is used, require + export are shimmed to keep things
consistent. However, it is important not to assume that a full Node.js
api is available. If you must know what JS engine you are using, you may
check for `USING_JSC` to be set as `true` when JavaScriptCore is used.
For examples of using the JS engines, see "csslint", "jslint", and
"jshint" in "SublimeLinter/sublimelinter/modules/libs" and the respective
python code of "css.py" and "javascript.py" in
"SublimeLinter/sublimelinter/modules".
- Douglas Crockford's JSLint Javascript linter (jslint) is now supported
(http://jslint.com). To use JSLint set the "javascript_linter" setting
to "jslint". You may also customize jslint behavior with the
"jslint_options" setting. For more information about options available
to JSLint, see http://jslint.com/lint.html.
- The CSSLint CSS linter (csslint) is now supported (http://csslint.net).
By default all CSSLint settings are turned on. You may customize csslint
behavior with the "csslint_options" setting. Please select
"Preferences->Package Settings->SublimeLinter->Settings - Default"
for more information on turning off or adjusting severity of tests.
For more information about options available to CSSLint, see
https://github.com/stubbornella/csslint/wiki/Rules.
SublimeLinter 1.6.1 changelog
=============================
CHANGES/FIXES
- Fixed an issue (#141) with JSLint running in Node.js
- Updated CSSLint, JSLint, JSHint to latest stable releases.
- Added additional debugging output (in Sublime console) when
errors occur running linters written in Javascript.
SublimeLinter 1.6.2 changelog
=============================
CHANGES/FIXES
- Replaced the default perl linter with Perl::Critic. The standard Perl syntax checker
can still be invoked by switching the "perl_linter" setting to "perl".
- Added a LICENSE file to define appropriate usage of SublimeLinter and its source.
- Converted README back to markdown.
IMPORTANT
Due to a vulnerability (issue #77) with the Perl linter, Perl syntax checking is no longer
enabled by default. The default linter for Perl has been replaced by Perl::Critic.
SublimeLinter 1.6.3 changelog
=============================
NEW FEATURES
- Support for `.jshintrc` files. If using JSHint, SublimeLinter
will recursively search the directory tree (from the file location
to the file-system root directory). This functionality is
specified in the JSHint README.
https://github.com/jshint/node-jshint/#within-your-projects-directory-tree
CHANGES/FIXES
- Fixed README reference in the menu.
- Updated CoffeeScript module to be compatible with the updated
coffee command in version 1.3.
IMPORTANT
If you are using the CoffeeScript linting, please upgrade
the installed coffee-script NPM module to 1.3 or greater.
npm update -g coffee-script
SublimeLinter 1.6.4 changelog
=============================
IMPORTANT!!
Please note that the SublimeLinter repository has moved to:
https://github.com/SublimeLinter/SublimeLinter
Issues and pull requests should be made there.
NEW FEATURES
- The Objective-J linter now catches spaces inside parentheses
and dependent clauses on the same line as a control structure.
CHANGES/FIXES
- The README has been reorganized to hopefully be clearer.
- More explicit Node.js installation instructions have been provided.
- The "pep8" setting is now recognized in SublimeLinter's settings.
- When a minimum delay is specified with the "sublimelinter_delay" setting,
SublimeLinter will only lint the currently displayed file when the
queued linters run. This allows you to avoid linting of files as they
are selected in the choose file palette.
SublimeLinter 1.6.5 changelog
=============================
NEW FEATURES
- Added a (Ruby) Haml linter based on `haml -c`. For more information about
Haml, please see http://haml.info.
- Added a simple Git commit message linter. This linter follows the rules as
defined by http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
CHANGES/FIXES
- Updated several links to point to the SublimeLinter's new Github location.
- "Ruby on Rails" syntax maps to "ruby" as part of the default settings.
- Linter arguments are now consistently defined as arrays (instead of tuples).
- Syntax map settings are no longer (sometimes) case-sensitive.
SublimeLinter 1.6.6 changelog
=============================
CHANGES/FIXES
- JSHint now shows underlines at the appropriate character positions when
using tabs for indentation.
- Upgrading CSSLint to the latest version (v0.9.8). This adds support for the
latest "Compatibility" options: "Disallow star hack" and "Disallow
underscore hack".
- Annotation highlighting is working again.
- Git Commit Message linting now ignores `git --diff` output in messages.
These lines are automatically generated and inserted when running
`git commit -v`.
SublimeLinter 1.6.7 changelog
=============================
NEW FEATURES
- Puppet linting is now supported via `puppet parser validate`.
- Added an option for more granular control of outline decorations. Set the
value of "sublimelinter_mark_style" to "outline", "fill", or "none" in
the user settings.
CHANGES/FIXES
- Repaired the built-in CSS linter (CSSLint). This was broken with with the
last update.
- Added missing documentation for "save-only" linting in the settings file.
- Adjusted ambiguous/misleading documentation for the
"sublimelinter_executable_map" setting.
SublimeLinter 1.6.8 changelog
=============================
NEW FEATURES
- HTML5 linting support via `tidy`. This linter will not run unless you have
a version of tidy with HTML5 support. To use this linter, please see:
https://github.com/w3c/tidy-html5
- XML linting via `xmllint`.
CHANGES/FIXES
- Made significant progress on issue (#181). However, SublimeLinter still
throws with some linter types on Windows 7 when a user has non-ascii
characters in the path (to the SL plugin).
- Updated PEP8 to v1.1
- Updated Pyflakes to v0.5.0
- Updated JSHint to latest stable (r11).
- Reverted a fix for accurate (JSHint) error column positions (when using tab
indentation) due to a regression with the `"white": true` option. You may
still manually fix error positions by setting `"indent": 1`.
- Changed (the default) background linting delay to a more sane 2 seconds.
This reduces memory usage, cpu processing, and visual noise while you are
actively writing code.
SublimeLinter 1.6.9 changelog
=============================
NEW FEATURES
- C/C++ lint via `cppcheck`. Also added alternative (hidden) support for
`cpplint.py`. Please see README for more info.
- Lua syntax check via `luac`.
CHANGES/FIXES
- Adding a 'beta' channel for SublimeLinter into Package Control. This
branch will act as a more formal method for testing new features and
fixes before a release.
SublimeLinter 1.6.10 changelog
==============================
CHANGES/FIXES
- Puppet validation supports error output for Puppet v3.0+.
- JSHint options now support the (proper) "globals" definition.
- Lua syntax check no longer creates luac.out file clutter.
- Clarified documentation for styling sublimelinter.annotations.
SublimeLinter 1.6.11 changelog
==============================
CHANGES/FIXES
- Github (nodeload) zip url scheme changed.

View File

@@ -0,0 +1,22 @@
{
"install": "messages/install.txt",
"1.5.1": "messages/1.5.1.txt",
"1.5.2": "messages/1.5.2.txt",
"1.5.3": "messages/1.5.3.txt",
"1.5.4": "messages/1.5.4.txt",
"1.5.5": "messages/1.5.5.txt",
"1.5.6": "messages/1.5.6.txt",
"1.5.7": "messages/1.5.7.txt",
"1.6.0": "messages/1.6.0.txt",
"1.6.1": "messages/1.6.1.txt",
"1.6.2": "messages/1.6.2.txt",
"1.6.3": "messages/1.6.3.txt",
"1.6.4": "messages/1.6.4.txt",
"1.6.5": "messages/1.6.5.txt",
"1.6.6": "messages/1.6.6.txt",
"1.6.7": "messages/1.6.7.txt",
"1.6.8": "messages/1.6.8.txt",
"1.6.9": "messages/1.6.9.txt",
"1.6.10": "messages/1.6.10.txt",
"1.6.11": "messages/1.6.11.txt"
}

View File

@@ -0,0 +1,85 @@
SublimeLinter 1.5.1 changelog
Please restart Sublime Text 2 after reading this.
NEW FEATURES
- SublimeLinter keeps its settings in its own settings file now:
SublimeLinter.sublime-settings. You will need to copy your
user settings to this file. To do so, follow these steps:
1. Select "Preferences->Settings - User" in one tab/window.
The title of this tab should be "Preferences.sublime-settings".
2. Open another tab/window and select "Preferences->Package Settings->
SublimeLinter->Settings - User". The title of this window should be
"SublimeLinter.sublime-settings".
3. Copy/cut any of the following settings from Preferences.sublime-settings
to SublimeLinter.sublime-settings:
sublimelinter
sublimelinter_executable_map
sublimelinter_syntax_map
sublimelinter_disable
sublimelinter_delay
sublimelinter_fill_outlines
sublimelinter_gutter_marks
sublimelinter_wrap_find
sublimelinter_popup_errors_on_save
javascript_linter
jshint_options
pep8_ignore
pyflakes_ignore
pyflakes_ignore_import_*
sublimelinter_objj_check_ascii
4. Save SublimeLinter.sublime-settings. The changes may not take
effect until you restart Sublime Text.
When changes are made to the user SublimeLinter settings, they
are immediately reloaded into every open view. Note that this will
override any temporary changes you may have made to the settings in
a given view.
- The google closure Javascript linter (gjslint) is now supported
(https://developers.google.com/closure/utilities/docs/linter_howto).
There is a new setting, "javascript_linter", which determines which
linter to use, jshint or gjslinter. You may also customize gjslint
behavior with the "gjslint_options" and "gjslint_ignore" settings.
Please select "Preferences->Package Settings->SublimeLinter->Settings - Default"
for more information on these settings.
- The color theme names have been changed to avoid clashes with
built in names.
Old New
--------------------- -----------------------------
sublimelinter.<type> sublimelinter.outline.<type>
invalid.<type> sublimelinter.underline.<type>
You will have to update your color themes accordingly. Please select
"Preferences->Package Settings->SublimeLinter->README" and search
for "Customizing colors" for more information.
- When selecting an error from the popup error list, the view is centered
on the error line.
CHANGES/FIXES
- The PHP error regex has been updated to work with PHP 5.3.8 on Mac OS X.
- The popup error list will no longer choke on non-ASCII text.
- Selecting an error from the popup error list no longer attempts to go
directly to the point of an error as this could not be done reliably.
It will jump to the first non-whitespace character of the error's line.
- Go to next/previous error works correctly when an error line has no underlines.
- If an exception is thrown by jshint (e.g. too many errors), the errors
captured up to that point are displayed.
- The built in jshint has been updated from the master jshint.
- Fixed errors that would occur with the popup error list when there was
more than error on a line.

View File

@@ -0,0 +1,5 @@
SublimeLinter 1.5.2 changelog
CHANGES/FIXES
- Fixed a problem with messages.json that prevented correct upgrading.

View File

@@ -0,0 +1,11 @@
SublimeLinter 1.5.3 changelog
Please restart Sublime Text 2 after reading this.
CHANGES/FIXES
- Annotations have been fixed.
- Entries in "sublimelinter_syntax_map" take precedence over built in mappings.
- Lint errors in PHP files will hopefully not be logged to the PHP log file.

View File

@@ -0,0 +1,10 @@
SublimeLinter 1.5.4 changelog
Please restart Sublime Text 2 after reading this.
CHANGES/FIXES
- jshint.js has been updated to the latest master version.
- [issue #128] An "unsafe" option has been added to jshint. If set true,
any UTF-8 characters are allowed in the source.

View File

@@ -0,0 +1,5 @@
SublimeLinter 1.5.5 changelog
CHANGES/FIXES
- The full change log is available from the SublimeLinter preferences menu.

View File

@@ -0,0 +1,5 @@
SublimeLinter 1.5.6 changelog
CHANGES/FIXES
- Fixed a problem with messages.json that prevented correct upgrading.

View File

@@ -0,0 +1,10 @@
SublimeLinter 1.5.7 changelog
CHANGES/FIXES
- node.js is the preferred Javascript engine on Mac OS X and will be used if it is installed.
JavaScriptCore does not handle non-ASCII text correctly and you should install node.js
if possible.
- If you imported BaseLinter.JSC_PATH, please change your linter to use the self.jsc_path()
method instead. JSC_PATH should no longer be considered public.

View File

@@ -0,0 +1,46 @@
SublimeLinter 1.6.0 changelog
NEW FEATURES
- Simpler abstraction of Javascript engines for JS powered linters.
To leverage a JS linter, include a "linter.js" file; this file should
`require` the actual linter library file and export a `lint` function.
The `lint` function should return a list of errors back to the python
language handler file (via the `errors` parameter to the `parse_errors`
method).
Although "linter.js" should follow the Node.js api, the linter may also
be run via JavaScriptCore on OS X if Node.js is not installed. In the case
where JavaScriptCore is used, require + export are shimmed to keep things
consistent. However, it is important not to assume that a full Node.js
api is available. If you must know what JS engine you are using, you may
check for `USING_JSC` to be set as `true` when JavaScriptCore is used.
For examples of using the JS engines, see "csslint", "jslint", and
"jshint" in "SublimeLinter/sublimelinter/modules/libs" and the respective
python code of "css.py" and "javascript.py" in
"SublimeLinter/sublimelinter/modules".
- Douglas Crockford's JSLint Javascript linter (jslint) is now supported
(http://jslint.com). To use JSLint set the "javascript_linter" setting
to "jslint". You may also customize jslint behavior with the
"jslint_options" setting. For more information about options available
to JSLint, see http://jslint.com/lint.html.
- The CSSLint CSS linter (csslint) is now supported (http://csslint.net).
By default all CSSLint settings are turned on. You may customize csslint
behavior with the "csslint_options" setting. Please select
"Preferences->Package Settings->SublimeLinter->Settings - Default"
for more information on turning off or adjusting severity of tests.
For more information about options available to CSSLint, see
https://github.com/stubbornella/csslint/wiki/Rules.
IMPORTANT
Do NOT edit the default SublimeLinter settings. Your changes will be lost
when SublimeLinter is updated. ALWAYS edit the user SublimeLinter settings
by selecting "Preferences->Package Settings->SublimeLinter->Settings - User".
Note that individual settings you include in your user settings will **completely**
replace the corresponding default setting, so you must provide that setting in its entirety.

View File

@@ -0,0 +1,20 @@
SublimeLinter 1.6.1 changelog
CHANGES/FIXES
- Fixed an issue (#141) with JSLint running in Node.js
- Updated CSSLint, JSLint, JSHint to latest stable releases.
- Added additional debugging output (in Sublime console) when
errors occur running linters written in Javascript.
IMPORTANT
Due to an issue (#133) with SublimeLinter v1.5.1, some users
have corrupt versions of SublimeLinter installed. Please check
to see if you have multiple listings for `SublimeLinter` in
`Preferences -> Package Settings`. If you do see 2 listings,
please run `Package Control: Upgrade/Overwrite All Packages`
from the Command Palette (`Tools -> Command Palette`).

View File

@@ -0,0 +1,11 @@
SublimeLinter 1.6.10 changelog
CHANGES/FIXES
- Puppet validation supports error output for Puppet v3.0+.
- JSHint options now support the (proper) "globals" definition.
- Lua syntax check no longer creates luac.out file clutter.
- Clarified documentation for styling sublimelinter.annotations.

View File

@@ -0,0 +1,5 @@
SublimeLinter 1.6.11 changelog
CHANGES/FIXES
- Github (nodeload) zip url scheme changed.

View File

@@ -0,0 +1,16 @@
SublimeLinter 1.6.2 changelog
CHANGES/FIXES
- Replaced the default perl linter with Perl::Critic. The standard Perl syntax checker
can still be invoked by switching the "perl_linter" setting to "perl".
- Added a LICENSE file to define appropriate usage of SublimeLinter and its source.
- Converted README back to markdown.
IMPORTANT
Due to a vulnerability (issue #77) with the Perl linter, Perl syntax checking is no longer
enabled by default. The default linter for Perl has been replaced by Perl::Critic.

View File

@@ -0,0 +1,26 @@
SublimeLinter 1.6.3 changelog
NEW FEATURES
- Support for `.jshintrc` files. If using JSHint, SublimeLinter
will recursively search the directory tree (from the file location
to the file-system root directory). This functionality is
specified in the JSHint README.
https://github.com/jshint/node-jshint/#within-your-projects-directory-tree
CHANGES/FIXES
- Fixed README reference in the menu.
- Updated CoffeeScript module to be compatible with the updated
coffee command in version 1.3.
IMPORTANT
If you are using the CoffeeScript linting, please upgrade
the installed coffee-script NPM module to 1.3 or greater.
npm update -g coffee-script

View File

@@ -0,0 +1,29 @@
SublimeLinter 1.6.4 changelog
IMPORTANT!!
Please note that the SublimeLinter repository has moved to:
https://github.com/SublimeLinter/SublimeLinter
Issues and pull requests should be made there.
NEW FEATURES
- The Objective-J linter now catches spaces inside parentheses
and dependent clauses on the same line as a control structure.
CHANGES/FIXES
- The README has been reorganized to hopefully be clearer.
- More explicit Node.js installation instructions have been provided.
- The "pep8" setting is now recognized in SublimeLinter's settings.
- When a minimum delay is specified with the "sublimelinter_delay" setting,
SublimeLinter will only lint the currently displayed file when the
queued linters run. This allows you to avoid linting of files as they
are selected in the choose file palette.

View File

@@ -0,0 +1,20 @@
SublimeLinter 1.6.5 changelog
NEW FEATURES
- Added a (Ruby) Haml syntax check based on `haml -c`. For more information
about Haml, please see http://haml.info.
- Added a simple Git commit message linter. This linter follows the rules as
defined by http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
CHANGES/FIXES
- Updated several links to point to the SublimeLinter's new Github location.
- "Ruby on Rails" syntax maps to "ruby" as part of the default settings.
- Linter arguments are now consistently defined as arrays (instead of tuples).
- Syntax map settings are no longer (sometimes) case-sensitive.

View File

@@ -0,0 +1,16 @@
SublimeLinter 1.6.6 changelog
CHANGES/FIXES
- JSHint now shows underlines at the appropriate character positions when
using tabs for indentation.
- Upgrading CSSLint to the latest version (v0.9.8). This adds support for the
latest "Compatibility" options: "Disallow star hack" and "Disallow
underscore hack".
- Annotation highlighting is working again.
- Git Commit Message linting now ignores `git --diff` output in messages.
These lines are automatically generated and inserted when running
`git commit -v`.

View File

@@ -0,0 +1,20 @@
SublimeLinter 1.6.7 changelog
NEW FEATURES
- Puppet syntax checking is now supported via `puppet parser validate`.
- Added an option for more granular control of outline decorations. Set the
value of "sublimelinter_mark_style" to "outline", "fill", or "none" in
the user settings.
CHANGES/FIXES
- Repaired the built-in CSS linter (CSSLint). This was broken with with the
last update.
- Added missing documentation for "save-only" linting in the settings file.
- Adjusted ambiguous/misleading documentation for the
"sublimelinter_executable_map" setting.

View File

@@ -0,0 +1,30 @@
SublimeLinter 1.6.8 changelog
NEW FEATURES
- HTML5 linting support via `tidy`. This linter will not run unless you have
a version of tidy with HTML5 support. To use this linter, please see:
https://github.com/w3c/tidy-html5
- XML linting via `xmllint`.
CHANGES/FIXES
- Made significant progress on issue (#181). However, SublimeLinter still
throws with some linter types on Windows 7 when a user has non-ascii
characters in the path (to the SL plugin).
- Updated PEP8 to v1.1
- Updated Pyflakes to v0.5.0
- Updated JSHint to latest stable (r11).
- Reverted a fix for accurate (JSHint) error column positions (when using tab
indentation) due to a regression with the `"white": true` option. You may
still manually fix error positions by setting `"indent": 1`.
- Changed (the default) background linting delay to a more sane 2 seconds.
This reduces memory usage, cpu processing, and visual noise while you are
actively writing code.

View File

@@ -0,0 +1,15 @@
SublimeLinter 1.6.9 changelog
NEW FEATURES
- C/C++ lint via `cppcheck`. Also added alternative (hidden) support for
`cpplint.py`. Please see README for more info.
- Lua syntax check via `luac`.
CHANGES/FIXES
- Adding a 'beta' channel for SublimeLinter into Package Control. This
branch will act as a more formal method for testing new features and
fixes before a release.

View File

@@ -0,0 +1,43 @@
SublimeLinter
=============
SublimeLinter is a plugin that supports "lint" programs (known as "linters"). SublimeLinter highlights
lines of code the linter deems to contain (potential) errors. It also
supports highlighting special annotations (for example: TODO) so that they
can be quickly located.
SublimeLinter has built in linters for the following languages:
* C/C++ - lint via `cppcheck`
* CoffeeScript - lint via `coffee -s -l`
* CSS - lint via built-in [csslint](http://csslint.net)
* Git Commit Messages - lint via built-in module based on [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
* Haml - syntax check via `haml -c`
* HTML - lint via `tidy` (actually [tidy for HTML5](http://w3c.github.com/tidy-html5/))
* Java - lint via `javac -Xlint`
* Javascript - lint via built in [jshint](http://jshint.org), [jslint](http://jslint.com), or the [closure linter (gjslint)](https://developers.google.com/closure/utilities/docs/linter_howto) (if installed)
* Lua - syntax check via `luac`
* Objective-J - lint via built-in [capp_lint](https://github.com/aparajita/capp_lint)
* Perl - lint via [Perl:Critic](http://perlcritic.com/) or syntax+deprecation check via `perl -c`
* PHP - syntax check via `php -l`
* Puppet - syntax check via `puppet parser validate`
* Python - native, moderately-complete lint
* Ruby - syntax check via `ruby -wc`
* XML - lint via `xmllint`
For more information:
---------------------
Please take the time to read the documentation:
* Online - https://github.com/SublimeLinter/SublimeLinter
* Sublime Text - Select Preferences->Package Settings->SublimeLinter->README
IMPORTANT
Do NOT edit the default SublimeLinter settings. Your changes will be lost
when SublimeLinter is updated. ALWAYS edit the user SublimeLinter settings
by selecting "Preferences->Package Settings->SublimeLinter->Settings - User".
Note that individual settings you include in your user settings will **completely**
replace the corresponding default setting, so you must provide that setting in its entirety.

View File

@@ -0,0 +1,34 @@
{
"schema_version": "1.1",
"packages": [
{
"name": "SublimeLinter",
"description": "Inline lint highlighting for the Sublime Text 2 editor",
"author": "Kronuz, Aparajita Fishman, Jake Swartwood",
"homepage": "http://github.com/SublimeLinter/SublimeLinter",
"last_modified": "2012-11-18 00:19:27",
"platforms": {
"*": [
{
"version": "1.6.11",
"url": "https://nodeload.github.com/SublimeLinter/SublimeLinter/zip/v1.6.11"
}
]
}
},
{
"name": "SublimeLinter Beta",
"description": "This version is considered unstable; only install this package if you plan on testing",
"author": "Kronuz, Aparajita Fishman, Jake Swartwood",
"homepage": "https://github.com/SublimeLinter/SublimeLinter/tree/beta",
"platforms": {
"*": [
{
"version": "1.7.0-beta.1",
"url": "https://nodeload.github.com/SublimeLinter/SublimeLinter/zip/beta"
}
]
}
}
]
}

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)