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