├── .gitignore ├── images └── screenshot.png ├── jslint4java-2.0.5-SNAPSHOT.jar ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── Default.sublime-commands ├── statusprocess.py ├── sublime-jslint.sublime-settings ├── LICENSE.md ├── Main.sublime-menu ├── asyncprocess.py ├── README.md ├── edit_buffer.py └── jslint.py /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | 3 | .DS_Store 4 | *.pyc 5 | 6 | *.sublime-project 7 | *.sublime-workspace -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbzhong/sublime-jslint/HEAD/images/screenshot.png -------------------------------------------------------------------------------- /jslint4java-2.0.5-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbzhong/sublime-jslint/HEAD/jslint4java-2.0.5-SNAPSHOT.jar -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+j"], 4 | "__doc__": "Run JSLint", 5 | "command": "jslint" 6 | } 7 | ] -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+j"], 4 | "__doc__": "Run JSLint", 5 | "command": "jslint" 6 | } 7 | ] -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+j"], 4 | "__doc__": "Run JSLint", 5 | "command": "jslint" 6 | } 7 | ] -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "JSLint: Run JSLint", 4 | "command": "jslint" 5 | }, 6 | { 7 | "caption": "JSLint: Show JSLint Result", 8 | "command": "show_jslint_result" 9 | }, 10 | { 11 | "caption": "Preferences: JSLint Settings – Default", 12 | "command": "open_file", "args": 13 | { 14 | "file": "${packages}/sublime-jslint/sublime-jslint.sublime-settings" 15 | } 16 | }, 17 | { 18 | "caption": "Preferences: JSLint Settings – User", 19 | "command": "open_file", "args": 20 | { 21 | "file": "${packages}/User/sublime-jslint.sublime-settings" 22 | } 23 | } 24 | ] -------------------------------------------------------------------------------- /statusprocess.py: -------------------------------------------------------------------------------- 1 | try: 2 | import thread 3 | except ImportError: 4 | import threading 5 | import functools 6 | import time 7 | import sublime 8 | 9 | 10 | class StatusProcess(object): 11 | def __init__(self, msg, listener): 12 | self.msg = msg 13 | self.listener = listener 14 | myThread = threading.Thread(target=self.run_thread) 15 | myThread.start() 16 | 17 | def run_thread(self): 18 | progress = "" 19 | while self.listener.is_running: 20 | if len(progress) >= 10: 21 | progress = "" 22 | progress += "." 23 | sublime.set_timeout(functools.partial(self.listener.update_status, self.msg, progress), 0) 24 | time.sleep(0.1) -------------------------------------------------------------------------------- /sublime-jslint.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | //Uses system installed jslint.js (node.js based), instead of bundled JSLint jar 3 | "use_node_jslint": false, 4 | 5 | //Path to the jslint.js 6 | //Leave blank to use default JSLint path 7 | "node_jslint_path": "", 8 | 9 | //Options passed to jslint.js 10 | "node_jslint_options": "", 11 | 12 | //Path to the JSLint jar. 13 | //Leave blank to use bundled jar. 14 | "jslint_jar": "", 15 | 16 | //Options passed to JSLint. 17 | "jslint_options": "", 18 | 19 | //Errors and RegEx to be ignored 20 | "ignore_errors": 21 | [ 22 | //"Expected an identifier and instead saw 'undefined' \(a reserved word\)" 23 | ], 24 | 25 | //Run JSLint on save. 26 | "run_on_save": false, 27 | 28 | //Debug flag. 29 | "debug": false 30 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | New BSD License 2 | =============== 3 | 4 | Copyright © 2011, Robin Zhong 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | * Neither the name of sublime-jslint nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ROBIN ZHONG BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "tools", 4 | "children": 5 | [ 6 | { 7 | "caption": "Lint", 8 | "id": "lint", 9 | "children": 10 | [ 11 | { 12 | "caption": "Run JSLint", 13 | "command": "jslint" 14 | }, 15 | { 16 | "caption": "Show JSLint Result", 17 | "command": "show_jslint_result" 18 | } 19 | ] 20 | } 21 | ] 22 | }, 23 | { 24 | "id": "preferences", 25 | "children": 26 | [ 27 | { 28 | "caption": "Package Settings", 29 | "id": "package-settings", 30 | "children": 31 | [ 32 | { 33 | "caption": "JSLint", 34 | "children": 35 | [ 36 | { 37 | "caption": "Settings – Default", 38 | "command": "open_file", 39 | "args": 40 | { 41 | "file": "${packages}/sublime-jslint/sublime-jslint.sublime-settings" 42 | } 43 | }, 44 | { 45 | "caption": "Settings – User", 46 | "command": "open_file", 47 | "args": 48 | { 49 | "file": "${packages}/User/sublime-jslint.sublime-settings" 50 | } 51 | } 52 | ] 53 | } 54 | ] 55 | } 56 | ] 57 | } 58 | ] -------------------------------------------------------------------------------- /asyncprocess.py: -------------------------------------------------------------------------------- 1 | import os 2 | try: 3 | import thread 4 | except ImportError: 5 | import threading 6 | import subprocess 7 | import functools 8 | import sublime 9 | import time 10 | 11 | 12 | class AsyncProcess(object): 13 | def __init__(self, cmd, listener): 14 | self.cmd = cmd 15 | self.listener = listener 16 | #print("DEBUG_EXEC: " + str(self.cmd)) 17 | self.proc = subprocess.Popen(self.cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 18 | 19 | try: 20 | if self.proc.stdout: 21 | thread.start_new_thread(self.read_stdout, ()) 22 | 23 | if self.proc.stderr: 24 | thread.start_new_thread(self.read_stderr, ()) 25 | 26 | thread.start_new_thread(self.poll, ()) 27 | except NameError: 28 | if self.proc.stdout: 29 | self.stdoutThread = threading.Thread(target=self.read_stdout) 30 | self.stdoutThread.start() 31 | 32 | if self.proc.stderr: 33 | self.stderrThread = threading.Thread(target=self.read_stderr) 34 | self.stderrThread.start() 35 | 36 | self.pollThread = threading.Thread(target=self.poll) 37 | self.pollThread.start() 38 | 39 | 40 | def poll(self): 41 | while True: 42 | if self.proc.poll() is not None: 43 | sublime.set_timeout(functools.partial(self.terminate), 0) 44 | break 45 | time.sleep(0.1) 46 | 47 | def read_stdout(self): 48 | while self.listener.is_running: 49 | data = os.read(self.proc.stdout.fileno(), 2**15) 50 | if data != b'': 51 | sublime.set_timeout(functools.partial(self.listener.append_data, self.proc, data), 0) 52 | 53 | def read_stderr(self): 54 | while self.listener.is_running: 55 | data = os.read(self.proc.stderr.fileno(), 2**15) 56 | if data != b'': 57 | sublime.set_timeout(functools.partial(self.listener.append_data, self.proc, data), 0) 58 | 59 | def terminate(self): 60 | sublime.set_timeout(functools.partial(self.listener.proc_terminated, self.proc), 0) 61 | self.listener.is_running = False 62 | self.pollThread.join() 63 | self.stdoutThread.join() 64 | self.proc.stdout.close() 65 | self.stderrThread.join() 66 | self.proc.stderr.close() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JSLint support for Sublime Text 2 by using jslint4java 2 | ====================================================== 3 | 4 | [Sublime Text 2](http://www.sublimetext.com/2) is a sophisticated text editor for code, HTML and prose. You'll love its slick user interface and extraordinary features. 5 | 6 | [JSLint4Java](http://code.google.com/p/jslint4java/) is a Java wrapper around the fabulous tool by Douglas Crockford, [JSLint](http://jslint.com). It provides a simple interface for detecting potential problems in JavaScript code. 7 | 8 | This project is a plugin to add JSLint support for Sublime Text 2. 9 | 10 | Features 11 | -------- 12 | 13 | * JSLint: Run JSLint (Ctrl+J), or run JSLint on save 14 | * JSLint: Show JSLint results 15 | * Highlight error line by click in the result view 16 | * Cross-platform: supports Windows, Linux and Mac OS X 17 | 18 | Requirements 19 | ------------ 20 | 21 | Java - also ensure that it has been added to PATH 22 | 23 | Installation 24 | ------------ 25 | 26 | * Using [Package Control](http://wbond.net/sublime_packages/package_control): 27 | * Install Package: sublime-jslint 28 | * Download and extract to Sublime Text 2 Packages folder 29 | * Windows: %APPDATA%\Sublime Text 2\Packages 30 | * Mac OS X: ~/Library/Application\ Support/Sublime\ Text\ 2/Packages/ 31 | * Linux: ~/.config/sublime-text-2/Packages 32 | 33 | How to use? 34 | ----------- 35 | 36 | Open the Command Palette (Windows and Linux: Ctrl+Shift+P, OSX: Command+Shift+P), then search for: 37 | 38 | * JSLint: Run JSLint (Ctrl+J) 39 | * JSLint: Show JSLint Result 40 | 41 | Open up a .js file and hit Ctrl+J to run JSLint. An new output panel will appear giving you the JSLint results: 42 | 43 | Screenshots 44 | ----------- 45 | 46 | ![](https://github.com/fbzhong/sublime-jslint/raw/master/images/screenshot.png) 47 | 48 | Settings 49 | -------- 50 | 51 | Settings can be opened via the Command Palette, or via the Preferences/Package Settings/JSLint/Settings – User menu entry. 52 | 53 | ```javascript 54 | { 55 | //Uses system installed jslint.js (node.js based), instead of bundled JSLint jar 56 | "use_node_jslint": false, 57 | 58 | //Path to the jslint.js 59 | //Leave blank to use default JSLint path 60 | "node_jslint_path": "", 61 | 62 | //Options passed to jslint.js 63 | "node_jslint_options": "", 64 | 65 | //Path to the JSLint jar. 66 | //Leave blank to use bundled jar. 67 | "jslint_jar": "", 68 | 69 | //Options passed to JSLint. 70 | "jslint_options": "", 71 | 72 | //Errors and RegEx to be ignored 73 | "ignore_errors": 74 | [ 75 | //"Expected an identifier and instead saw 'undefined' \(a reserved word\)" 76 | ], 77 | 78 | //Run JSLint on save. 79 | "run_on_save": false, 80 | 81 | //Debug flag. 82 | "debug": false 83 | } 84 | ``` 85 | 86 | All available jslint_options can be found [here](https://github.com/fbzhong/sublime-jslint/wiki/Available-jslint4java-options). 87 | 88 | License 89 | ------- 90 | 91 | sublime-jslint is released under the New BSD License, which may be found [here](https://github.com/fbzhong/sublime-jslint/blob/master/LICENSE.md). -------------------------------------------------------------------------------- /edit_buffer.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import sublime 3 | import sublime_plugin 4 | 5 | try: 6 | sublime.edit_storage 7 | except AttributeError: 8 | sublime.edit_storage = {} 9 | 10 | 11 | def run_callback(func, *args, **kwargs): 12 | spec = inspect.getfullargspec(func) 13 | if spec.args or spec.varargs: 14 | func(*args, **kwargs) 15 | else: 16 | func() 17 | 18 | 19 | class EditFuture: 20 | def __init__(self, func): 21 | self.func = func 22 | 23 | def resolve(self, view, edit): 24 | return self.func(view, edit) 25 | 26 | 27 | class EditStep: 28 | def __init__(self, cmd, *args): 29 | self.cmd = cmd 30 | self.args = args 31 | 32 | def run(self, view, edit): 33 | if self.cmd == 'callback': 34 | return run_callback(self.args[0], view, edit) 35 | 36 | funcs = { 37 | 'insert': view.insert, 38 | 'erase': view.erase, 39 | 'replace': view.replace, 40 | } 41 | func = funcs.get(self.cmd) 42 | if func: 43 | args = self.resolve_args(view, edit) 44 | func(edit, *args) 45 | 46 | def resolve_args(self, view, edit): 47 | args = [] 48 | for arg in self.args: 49 | if isinstance(arg, EditFuture): 50 | arg = arg.resolve(view, edit) 51 | args.append(arg) 52 | return args 53 | 54 | 55 | class Edit: 56 | def __init__(self, view, read_only): 57 | self.view = view 58 | self.read_only = read_only 59 | self.steps = [] 60 | 61 | def __nonzero__(self): 62 | return bool(self.steps) 63 | 64 | @classmethod 65 | def future(self, func): 66 | return EditFuture(func) 67 | 68 | def step(self, cmd, *args): 69 | step = EditStep(cmd, *args) 70 | self.steps.append(step) 71 | 72 | def insert(self, point, string): 73 | self.step('insert', point, string) 74 | 75 | def erase(self, region): 76 | self.step('erase', region) 77 | 78 | def replace(self, region, string): 79 | self.step('replace', region, string) 80 | 81 | def sel(self, start, end=None): 82 | if end is None: 83 | end = start 84 | self.step('sel', start, end) 85 | 86 | def callback(self, func): 87 | self.step('callback', func) 88 | 89 | def run(self, view, edit): 90 | for step in self.steps: 91 | step.run(view, edit) 92 | 93 | def __enter__(self): 94 | return self 95 | 96 | def __exit__(self, type, value, traceback): 97 | view = self.view 98 | if(self.read_only): 99 | view.set_read_only(False) 100 | if sublime.version().startswith('2'): 101 | edit = view.begin_edit() 102 | self.run(view, edit) 103 | view.end_edit(edit) 104 | else: 105 | key = str(hash(tuple(self.steps))) 106 | sublime.edit_storage[key] = self.run 107 | view.run_command('apply_edit', {'key': key}) 108 | if(self.read_only): 109 | view.set_read_only(True) 110 | 111 | 112 | class apply_edit(sublime_plugin.TextCommand): 113 | def run(self, edit, key): 114 | sublime.edit_storage.pop(key)(self.view, edit) -------------------------------------------------------------------------------- /jslint.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sublime 4 | import sublime_plugin 5 | try: 6 | from edit_buffer import * 7 | from statusprocess import * 8 | from asyncprocess import * 9 | except ImportError: 10 | from .edit_buffer import * 11 | from .statusprocess import * 12 | from .asyncprocess import * 13 | 14 | RESULT_VIEW_NAME = 'jslint_result_view' 15 | SETTINGS_FILE = "sublime-jslint.sublime-settings" 16 | 17 | 18 | class ShowJslintResultCommand(sublime_plugin.WindowCommand): 19 | """show jslint result""" 20 | def run(self): 21 | self.window.run_command("show_panel", {"panel": "output." + RESULT_VIEW_NAME}) 22 | 23 | 24 | class JslintCommand(sublime_plugin.WindowCommand): 25 | def run(self): 26 | s = sublime.load_settings(SETTINGS_FILE) 27 | 28 | file_path = self.window.active_view().file_name() 29 | file_name = os.path.basename(file_path) 30 | 31 | self.debug = s.get('debug', False) 32 | self.buffered_data = '' 33 | self.file_path = file_path 34 | self.file_name = file_name 35 | self.is_running = True 36 | self.tests_panel_showed = False 37 | self.ignored_error_count = 0 38 | self.ignore_errors = s.get('ignore_errors', []) 39 | self.use_node_jslint = s.get('use_node_jslint', False) 40 | 41 | self.init_tests_panel() 42 | 43 | if (self.use_node_jslint): 44 | cmd = 'jslint ' + s.get('node_jslint_options', '') + ' "' + file_path + '"' 45 | else: 46 | if len(s.get('jslint_jar', '')) > 0: 47 | jslint_jar = s.get('jslint_jar') 48 | else: 49 | jslint_jar = sublime.packages_path() + '/sublime-jslint/jslint4java-2.0.5-SNAPSHOT.jar' 50 | cmd = 'java -jar "' + jslint_jar + '" ' + s.get('jslint_options', '') + ' "' + file_path + '"' 51 | 52 | AsyncProcess(cmd, self) 53 | StatusProcess('Starting JSLint for file ' + file_name, self) 54 | 55 | JsLintEventListener.disabled = True 56 | 57 | def init_tests_panel(self): 58 | if not hasattr(self, 'output_view'): 59 | self.output_view = self.window.get_output_panel(RESULT_VIEW_NAME) 60 | self.output_view.set_name(RESULT_VIEW_NAME) 61 | self.clear_test_view() 62 | self.output_view.settings().set("file_path", self.file_path) 63 | 64 | def show_tests_panel(self): 65 | if self.tests_panel_showed: 66 | return 67 | self.window.run_command("show_panel", {"panel": "output."+RESULT_VIEW_NAME}) 68 | self.tests_panel_showed = True 69 | 70 | def clear_test_view(self): 71 | with Edit(self.output_view, True) as edit: 72 | edit.erase(sublime.Region(0, self.output_view.size())) 73 | 74 | def append_data(self, proc, bData, end=False): 75 | if self.debug: 76 | print("DEBUG: append_data start") 77 | data = bData.decode('utf-8') 78 | if self.debug: 79 | print("DEBUG: data= "+data) 80 | self.buffered_data = self.buffered_data + data 81 | data = self.buffered_data.replace(self.file_path, self.file_name).replace('\r\n', '\n').replace('\r', '\n') 82 | 83 | if end is False: 84 | rsep_pos = data.rfind('\n') 85 | if rsep_pos == -1: 86 | # not found full line. 87 | return 88 | self.buffered_data = data[rsep_pos+1:] 89 | data = data[:rsep_pos+1] 90 | 91 | # ignore error. 92 | text = data 93 | if (len(self.ignore_errors) > 0) and (not self.use_node_jslint): 94 | text = '' 95 | for line in data.split('\n'): 96 | if len(line) == 0: 97 | continue 98 | ignored = False 99 | for rule in self.ignore_errors: 100 | if re.search(rule, line): 101 | ignored = True 102 | self.ignored_error_count += 1 103 | if self.debug: 104 | print("text match line ") 105 | print("rule = " + rule) 106 | print("line = " + line) 107 | print("---------") 108 | break 109 | if ignored is False: 110 | text += line + '\n' 111 | 112 | self.show_tests_panel() 113 | selection_was_at_end = (len(self.output_view.sel()) == 1 and self.output_view.sel()[0] == sublime.Region(self.output_view.size())) 114 | with Edit(self.output_view, True) as edit: 115 | edit.insert(self.output_view.size(), text) 116 | 117 | if end and not self.use_node_jslint: 118 | text = '\njslint: ignored ' + str(self.ignored_error_count) + ' errors.\n\n' 119 | with Edit(self.output_view, True) as edit: 120 | edit.insert(0, text) 121 | 122 | def update_status(self, msg, progress): 123 | sublime.status_message(msg + " " + progress) 124 | 125 | def proc_terminated(self, proc): 126 | if proc.returncode == 0: 127 | msg = self.file_name + ' lint free!' 128 | else: 129 | msg = '' 130 | self.append_data(proc, msg.encode('utf-8'), True) 131 | 132 | JsLintEventListener.disabled = False 133 | 134 | 135 | class JsLintEventListener(sublime_plugin.EventListener): 136 | """jslint event""" 137 | disabled = False 138 | 139 | def __init__(self): 140 | self.previous_resion = None 141 | self.file_view = None 142 | 143 | def on_post_save(self, view): 144 | s = sublime.load_settings(SETTINGS_FILE) 145 | if s.get('run_on_save', False) is False: 146 | return 147 | 148 | if view.file_name().endswith('.js') is False: 149 | return 150 | 151 | # run jslint. 152 | sublime.active_window().run_command("jslint") 153 | 154 | def on_deactivated(self, view): 155 | if view.name() != RESULT_VIEW_NAME: 156 | return 157 | self.previous_resion = None 158 | 159 | if self.file_view: 160 | self.file_view.erase_regions(RESULT_VIEW_NAME) 161 | 162 | def on_selection_modified(self, view): 163 | if JsLintEventListener.disabled: 164 | return 165 | if view.name() != RESULT_VIEW_NAME: 166 | return 167 | region = view.line(view.sel()[0]) 168 | s = sublime.load_settings(SETTINGS_FILE) 169 | 170 | # make sure call once. 171 | if self.previous_resion == region: 172 | return 173 | self.previous_resion = region 174 | 175 | # extract line from jslint result. 176 | if (s.get('use_node_jslint', False)): 177 | pattern_position = "\\/\\/ Line (\d+), Pos (\d+)$" 178 | text = view.substr(region) 179 | text = re.findall(pattern_position, text) 180 | if len(text) > 0: 181 | line = int(text[0][0]) 182 | col = int(text[0][1]) 183 | else: 184 | text = view.substr(region).split(':') 185 | if len(text) < 4 or text[0] != 'jslint' or re.match('\d+', text[2]) is None or re.match('\d+', text[3]) is None: 186 | return 187 | line = int(text[2]) 188 | col = int(text[3]) 189 | 190 | # hightligh view line. 191 | view.add_regions(RESULT_VIEW_NAME, [region], "comment") 192 | 193 | # find the file view. 194 | file_path = view.settings().get('file_path') 195 | window = sublime.active_window() 196 | file_view = None 197 | for v in window.views(): 198 | if v.file_name() == file_path: 199 | file_view = v 200 | break 201 | if file_view is None: 202 | return 203 | 204 | self.file_view = file_view 205 | window.focus_view(file_view) 206 | file_view.run_command("goto_line", {"line": line}) 207 | file_region = file_view.line(file_view.sel()[0]) 208 | 209 | # highlight file_view line 210 | file_view.add_regions(RESULT_VIEW_NAME, [file_region], "string") --------------------------------------------------------------------------------