├── .gitignore ├── .no-sublime-package ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── Default.sublime-commands ├── Main.sublime-menu ├── PHP_CodeSniffer.py ├── PHP_CodeSniffer.sublime-settings ├── README.md ├── STPluginReport.php └── icons ├── error.png └── warning.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | -------------------------------------------------------------------------------- /.no-sublime-package: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squizlabs/sublime-PHP_CodeSniffer/e7fa40d3a7ad27553e8caf0014567cace82ed785/.no-sublime-package -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["alt+s"], 4 | "command": "phpcs" 5 | }, 6 | { 7 | "keys": ["alt+shift+s"], 8 | "command": "phpcbf" 9 | } 10 | ] -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["alt+s"], 4 | "command": "phpcs" 5 | }, 6 | { 7 | "keys": ["alt+shift+s"], 8 | "command": "phpcbf" 9 | } 10 | ] -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["alt+s"], 4 | "command": "phpcs" 5 | }, 6 | { 7 | "keys": ["alt+shift+s"], 8 | "command": "phpcbf" 9 | } 10 | ] -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "PHP_CodeSniffer: Check File", 4 | "command": "phpcs" 5 | }, 6 | { 7 | "caption": "PHP_CodeSniffer: Fix File", 8 | "command": "phpcbf" 9 | }, 10 | { 11 | "caption": "Preferences: PHP_CodeSniffer Settings – Default", 12 | "command": "open_file", "args": 13 | { 14 | "file": "${packages}/PHP_CodeSniffer/PHP_CodeSniffer.sublime-settings" 15 | } 16 | }, 17 | { 18 | "caption": "Preferences: PHP_CodeSniffer Settings – User", 19 | "command": "open_file", "args": 20 | { 21 | "file": "${packages}/User/PHP_CodeSniffer.sublime-settings" 22 | } 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "tools", 4 | "children": 5 | [ 6 | { 7 | "caption": "PHP_CodeSniffer", 8 | "id": "phpcs", 9 | "children": 10 | [ 11 | { 12 | "caption": "Check File", 13 | "command": "phpcs" 14 | }, 15 | { 16 | "caption": "Fix File", 17 | "command": "phpcbf" 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": "PHP_CodeSniffer", 34 | "children": 35 | [ 36 | { 37 | "caption": "Settings – Default", 38 | "command": "open_file", 39 | "args": 40 | { 41 | "file": "${packages}/PHP_CodeSniffer/PHP_CodeSniffer.sublime-settings" 42 | } 43 | }, 44 | { 45 | "caption": "Settings – User", 46 | "command": "open_file", 47 | "args": 48 | { 49 | "file": "${packages}/User/PHP_CodeSniffer.sublime-settings" 50 | } 51 | } 52 | ] 53 | } 54 | ] 55 | } 56 | ] 57 | } 58 | ] 59 | -------------------------------------------------------------------------------- /PHP_CodeSniffer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sublime 4 | import sublime_plugin 5 | import subprocess 6 | import string 7 | import difflib 8 | import threading 9 | 10 | SETTINGS_FILE = 'PHP_CodeSniffer.sublime-settings' 11 | RESULT_VIEW_NAME = 'phpcs_result_view' 12 | 13 | settings = sublime.load_settings(SETTINGS_FILE) 14 | 15 | class PHP_CodeSniffer: 16 | # Type of the view, phpcs or phpcbf. 17 | file_view = None 18 | view_type = None 19 | window = None 20 | processed = False 21 | output_view = None 22 | process_anim_idx = 0 23 | process_anim = { 24 | 'windows': ['|', '/', '-', '\\'], 25 | 'linux': ['|', '/', '-', '\\'], 26 | 'osx': [u'\u25d0', u'\u25d3', u'\u25d1', u'\u25d2'] 27 | } 28 | regions = [] 29 | 30 | def run(self, window, cmd, msg): 31 | self.window = window 32 | content = window.active_view().substr(sublime.Region(0, window.active_view().size())) 33 | 34 | tm = threading.Thread(target=self.loading_msg, args=([msg])) 35 | tm.start() 36 | 37 | t = threading.Thread(target=self.run_command, args=(self.get_command_args(cmd), cmd, content, window, window.active_view().file_name())) 38 | t.start() 39 | 40 | 41 | def loading_msg(self, msg): 42 | sublime.set_timeout(lambda: self.show_loading_msg(msg), 0) 43 | 44 | 45 | def process_phpcbf_results(self, fixed_content, window, content): 46 | # Remove the gutter markers. 47 | self.window = window 48 | self.file_view = window.active_view() 49 | self.view_type = 'phpcbf' 50 | 51 | # Get the diff between content and the fixed content. 52 | difftxt = self.run_diff(window, content, fixed_content) 53 | self.processed = True 54 | 55 | if not difftxt: 56 | self.clear_view() 57 | return 58 | 59 | self.file_view.erase_regions('errors') 60 | self.file_view.erase_regions('warnings') 61 | 62 | # Store the current viewport position. 63 | scrollPos = self.file_view.viewport_position() 64 | 65 | # Show diff text in the results panel. 66 | self.show_results_view(window, difftxt) 67 | self.set_status_msg(''); 68 | 69 | self.file_view.run_command('set_view_content', {'data':fixed_content, 'replace':True}) 70 | 71 | # After the active view contents are changed set the scroll position back to previous position. 72 | self.file_view.set_viewport_position(scrollPos, False) 73 | 74 | 75 | def run_diff(self, window, origContent, fixed_content): 76 | try: 77 | a = origContent.splitlines() 78 | b = fixed_content.splitlines() 79 | except UnicodeDecodeError as e: 80 | sublime.status_message("Diff only works with UTF-8 files") 81 | return 82 | 83 | # Get the diff between original content and the fixed content. 84 | diff = difflib.unified_diff(a, b, 'Original', 'Fixed', lineterm='') 85 | difftxt = u"\n".join(line for line in diff) 86 | 87 | if difftxt == "": 88 | sublime.status_message('PHP_CodeSniffer did not make any changes') 89 | return 90 | 91 | difftxt = "\n PHP_CodeSniffer made the following fixes to this file:\n\n" + difftxt 92 | return difftxt 93 | 94 | 95 | def process_phpcs_results(self, data, window): 96 | self.processed = True 97 | self.window = window 98 | self.file_view = window.active_view() 99 | self.view_type = 'phpcs' 100 | 101 | if data == '': 102 | self.file_view.erase_regions('errors') 103 | self.file_view.erase_regions('warnings') 104 | window.run_command("hide_panel", {"panel": "output." + RESULT_VIEW_NAME}) 105 | self.set_status_msg('No errors or warnings detected.') 106 | return 107 | 108 | self.show_results_view(window, data) 109 | self.set_status_msg(''); 110 | 111 | # Add gutter markers for each error. 112 | lines = data.decode('utf-8').split("\n") 113 | err_regions = [] 114 | warn_regions = [] 115 | col_regions = [] 116 | msg_type = '' 117 | 118 | for line in lines: 119 | if line.find('Errors:') != -1: 120 | msg_type = 'error' 121 | elif line.find('Warnings:') != -1: 122 | msg_type = 'warning' 123 | else: 124 | match = re.match(r'[^:0-9]+([0-9]+)\s*:', line) 125 | if match: 126 | pt = window.active_view().text_point(int(match.group(1)) - 1, 0) 127 | r = window.active_view().line(pt) 128 | self.regions.append( 129 | { 130 | 'region': r, 131 | 'type': msg_type, 132 | 'message': line 133 | } 134 | ); 135 | 136 | if msg_type == 'error': 137 | err_regions.append(r) 138 | else: 139 | warn_regions.append(r) 140 | 141 | window.active_view().erase_regions('errors') 142 | window.active_view().erase_regions('warnings') 143 | window.active_view().add_regions('errors', err_regions, settings.get('error_scope'), 'Packages/PHP_CodeSniffer/icons/error.png', sublime.DRAW_STIPPLED_UNDERLINE | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE) 144 | window.active_view().add_regions('warnings', warn_regions, settings.get('warning_scope'), 'Packages/PHP_CodeSniffer/icons/warning.png', sublime.DRAW_STIPPLED_UNDERLINE | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE) 145 | 146 | def get_command_args(self, cmd_type): 147 | args = [] 148 | 149 | if settings.get('php_path'): 150 | args.append(settings.get('php_path')) 151 | elif os.name == 'nt': 152 | args.append('php') 153 | 154 | if cmd_type == 'phpcs': 155 | args.append(settings.get('phpcs_path', 'phpcs')) 156 | args.append('--report=' + sublime.packages_path() + '/PHP_CodeSniffer/STPluginReport.php') 157 | else: 158 | args.append(settings.get('phpcbf_path', 'phpcbf')) 159 | 160 | standard_setting = settings.get('phpcs_standard') 161 | standard = '' 162 | 163 | if type(standard_setting) is dict: 164 | for folder in self.window.folders(): 165 | folder_name = os.path.basename(folder) 166 | if folder_name in standard_setting: 167 | standard = standard_setting[folder_name] 168 | break 169 | 170 | if standard == '' and '_default' in standard_setting: 171 | standard = standard_setting['_default'] 172 | else: 173 | standard = standard_setting 174 | 175 | if settings.get('phpcs_standard'): 176 | args.append('--standard=' + standard) 177 | 178 | args.append('-') 179 | 180 | if settings.get('additional_args'): 181 | args += settings.get('additional_args') 182 | 183 | return args 184 | 185 | def run_command(self, args, cmd, content, window, file_path): 186 | shell = False 187 | if os.name == 'nt': 188 | shell = True 189 | 190 | self.processed = False 191 | proc = subprocess.Popen(args, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) 192 | 193 | if file_path: 194 | phpcs_content = 'phpcs_input_file: ' + file_path + "\n" + content; 195 | else: 196 | phpcs_content = content; 197 | 198 | if proc.stdout: 199 | data = proc.communicate(phpcs_content.encode('utf-8'))[0] 200 | 201 | if cmd == 'phpcs': 202 | sublime.set_timeout(lambda: self.process_phpcs_results(data, window), 0) 203 | else: 204 | data = data.decode('utf-8') 205 | sublime.set_timeout(lambda: self.process_phpcbf_results(data, window, content), 0) 206 | 207 | def init_results_view(self, window): 208 | self.output_view = window.get_output_panel(RESULT_VIEW_NAME) 209 | self.output_view.set_syntax_file('Packages/Diff/Diff.tmLanguage') 210 | self.output_view.set_name(RESULT_VIEW_NAME) 211 | self.output_view.settings().set('gutter', False) 212 | 213 | self.clear_view() 214 | self.output_view.settings().set("file_path", window.active_view().file_name()) 215 | return self.output_view 216 | 217 | def show_results_view(self, window, data): 218 | if sublime.version().startswith('2'): 219 | data = data.decode('utf-8').replace('\r', '') 220 | else: 221 | if type(data) is bytes: 222 | data = data.decode('utf-8').replace('\r', '') 223 | 224 | outputView = self.init_results_view(window) 225 | window.run_command("show_panel", {"panel": "output." + RESULT_VIEW_NAME}) 226 | outputView.set_read_only(False) 227 | 228 | self.output_view.run_command('set_view_content', {'data':data}) 229 | 230 | outputView.set_read_only(True) 231 | 232 | 233 | def set_status_msg(self, msg): 234 | sublime.status_message(msg) 235 | 236 | def show_loading_msg(self, msg): 237 | if self.processed == True: 238 | return 239 | 240 | msg = msg[:-2] 241 | msg = msg + ' ' + self.process_anim[sublime.platform()][self.process_anim_idx] 242 | 243 | self.process_anim_idx += 1; 244 | if self.process_anim_idx > (len(self.process_anim[sublime.platform()]) - 1): 245 | self.process_anim_idx = 0 246 | 247 | self.set_status_msg(msg) 248 | sublime.set_timeout(lambda: self.show_loading_msg(msg), 300) 249 | 250 | 251 | def clear_view(self): 252 | if self.output_view != None: 253 | self.output_view.set_read_only(False) 254 | self.output_view.run_command('set_view_content', {'data':''}) 255 | self.output_view.set_read_only(True) 256 | 257 | self.file_view.erase_regions('errors') 258 | self.file_view.erase_regions('warnings') 259 | 260 | 261 | def line_clicked(self): 262 | if self.view_type == 'phpcs': 263 | self.handle_phpcs_line_click() 264 | else: 265 | self.handle_phpcbf_line_click() 266 | 267 | 268 | def handle_phpcs_line_click(self): 269 | region = self.output_view.line(self.output_view.sel()[0]) 270 | line = self.output_view.substr(region) 271 | 272 | if line.find('[ Click here to fix this file ]') != -1: 273 | self.run(self.window, 'phpcbf', 'Runnings PHPCS Fixer ') 274 | return 275 | else: 276 | match = re.match(r'[^:0-9]+([0-9]+)\s*:', line) 277 | if not match: 278 | return 279 | 280 | # Highlight the clicked results line. 281 | self.output_view.add_regions(RESULT_VIEW_NAME, [region], "comment", 'bookmark', sublime.DRAW_OUTLINED) 282 | 283 | lineNum = match.group(1) 284 | self.go_to_line(lineNum) 285 | 286 | 287 | def handle_phpcbf_line_click(self): 288 | pnt = self.output_view.sel()[0] 289 | region = self.output_view.line(pnt) 290 | line = self.output_view.substr(region) 291 | (row, col) = self.output_view.rowcol(pnt.begin()) 292 | 293 | offset = 0 294 | found = False 295 | while not found and row > 0: 296 | text_point = self.output_view.text_point(row, 0) 297 | line = self.output_view.substr(self.output_view.line(text_point)) 298 | if line.startswith('@@'): 299 | match = re.match(r'^@@ -\d+,\d+ \+(\d+),.*', line) 300 | if match: 301 | lineNum = int(match.group(1)) + offset - 1 302 | self.go_to_line(lineNum) 303 | 304 | break 305 | elif not line.startswith('-'): 306 | offset = offset + 1 307 | 308 | row = row - 1 309 | 310 | 311 | def go_to_line(self, lineNum): 312 | self.window.focus_view(self.file_view) 313 | self.file_view.run_command("goto_line", {"line": lineNum}) 314 | 315 | def showPopup(self, view, sublime, point): 316 | if self.output_view is None or self.output_view.window() is None: 317 | return 318 | 319 | styles = '' 322 | 323 | for region in self.regions: 324 | if region.get('region').contains(point): 325 | content = styles + '
'; 326 | content += '

' + region.get('message').split(':')[1] + '

' 327 | content += '
' 328 | 329 | view.show_popup( 330 | content, 331 | flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY, 332 | location=point, 333 | max_width=1200, 334 | max_height=500, 335 | ) 336 | break 337 | 338 | class set_view_content(sublime_plugin.TextCommand): 339 | def run(self, edit, data, replace=False): 340 | if replace == True: 341 | self.view.replace(edit, sublime.Region(0, self.view.size()), data) 342 | else: 343 | self.view.insert(edit, 0, data) 344 | 345 | 346 | # Init PHPCS. 347 | phpcs = PHP_CodeSniffer() 348 | 349 | class PhpcbfCommand(sublime_plugin.WindowCommand): 350 | def run(self): 351 | phpcs.run(self.window, 'phpcbf', 'Runnings PHPCS Fixer ') 352 | 353 | class PhpcsCommand(sublime_plugin.WindowCommand): 354 | def run(self): 355 | phpcs.run(self.window, 'phpcs', 'Runnings PHPCS ') 356 | 357 | 358 | class PhpcsEventListener(sublime_plugin.EventListener): 359 | def __init__(self): 360 | self.previous_region = None 361 | 362 | def on_query_context(self, view, key, operator, operand, match_all): 363 | # TODO: No idea if this is the right way but seems to work o.O 364 | if key == 'panel_visible': 365 | view.erase_regions('errors') 366 | view.erase_regions('warnings') 367 | 368 | def on_post_save(self, view): 369 | if settings.get('run_on_save', False) == False: 370 | return 371 | 372 | if view.file_name().endswith('.inc') == False: 373 | return 374 | 375 | sublime.active_window().run_command("phpcs") 376 | 377 | def on_selection_modified(self, view): 378 | if view.name() != RESULT_VIEW_NAME: 379 | return 380 | 381 | region = view.line(view.sel()[0]) 382 | 383 | if self.previous_region == region: 384 | return 385 | 386 | self.previous_region = region 387 | phpcs.line_clicked() 388 | 389 | def on_hover(self, view, point, hover_zone): 390 | if hover_zone == sublime.HOVER_GUTTER: 391 | phpcs.showPopup(view, sublime, point) 392 | 393 | def plugin_loaded(): 394 | global settings 395 | settings = sublime.load_settings(SETTINGS_FILE) 396 | -------------------------------------------------------------------------------- /PHP_CodeSniffer.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // Run PHPCS when a buffer is saved. 3 | "run_on_save": false, 4 | 5 | // Path to PHP. 6 | "php_path": "", 7 | 8 | // Path to the PHPCS script. 9 | "phpcs_path": "/usr/local/bin/phpcs", 10 | 11 | // Path to the PHPCBF script. 12 | "phpcbf_path": "/usr/local/bin/phpcbf", 13 | 14 | // PHPCS Standard to Use. Can be a string or a dict (folder => standard). 15 | "phpcs_standard": "Squiz", 16 | 17 | // Additional arguments to pass to PHPCS/PHPCBF. 18 | "additional_args": [], 19 | 20 | // Gutter error icon colour. 21 | "error_scope": "comment.block", 22 | 23 | // Gutter warning icon colour. 24 | "warning_scope": "function" 25 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP_CodeSniffer Sublime Text 2/3 Plugin 2 | ======================================== 3 | PHP_CodeSniffer Sublime Text Plugin allows running of [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) inside Sublime Text. 4 | 5 | Running the PHPCS command displays the coding standard violations report and displays gutter markers for the lines that have code violations. 6 | 7 | PHPCS screenshot 8 | 9 | Running the PHPCBF command attempts to fix the coding standard violations and displays a diff of the changes that were made. 10 | 11 | PHPCS Fixer screenshot 12 | 13 | 14 | 15 | Installation 16 | -------------- 17 | - Install [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). 18 | - Clone the PHP_CodeSniffer Sublime Text Plugin in to ST2/ST3 Packages directory. 19 | ``` 20 | git clone https://github.com/squizlabs/sublime-PHP_CodeSniffer PHP_CodeSniffer 21 | ``` 22 | - Packages directory locations: 23 | ``` 24 | Mac: /Users/{user}/Library/Application Support/Sublime Text 2/Packages 25 | Windows: C:\Users\{user}\AppData\Roaming\Sublime Text 2\Packages 26 | Linux: ~/.config/sublime-text-2/Packages 27 | ``` 28 | 29 | Configuration 30 | -------------- 31 | Configuration files can be opened via Preferences > Package Settings > PHP_CodeSniffer. 32 | 33 | Make sure the php_path, phpcs_path and phpcbf_path paths are correct. E.g. 34 | ``` 35 | "phpcs_path": "/usr/local/bin/phpcs", 36 | "phpcbf_path": "/usr/local/bin/phpcbf", 37 | ``` 38 | 39 | 40 | **phpcs_standard** 41 | 42 | This settings can be the name of a single standard or a list of folder/project names and the standard to be used for each project. E.g. 43 | ``` 44 | "phpcs_standard": "Squiz" 45 | ``` 46 | ``` 47 | "phpcs_standard": { 48 | "PHP_CodeSniffer": "PHPCS", 49 | "php-sikuli": "PSR1", 50 | "Sublime-PHP_CodeSniffer": "PEAR" 51 | } 52 | ``` 53 | 54 | **additional_args** 55 | 56 | Array containing additional arguments to pass to the PHPCS/PHPCBF scripts. 57 | 58 | **error_scope & warning_scope** 59 | 60 | These settings define the colors used for the error and warning gutter markers. 61 | ``` 62 | // Gutter error icon color. 63 | "error_scope": "comment.block", 64 | 65 | // Gutter warning icon color. 66 | "warning_scope": "function" 67 | ``` 68 | 69 | **run_on_save** 70 | 71 | If set to *true* then buffer will be checked on each save. 72 | 73 | 74 | Usage 75 | -------- 76 | There are two shortcuts that can be used for Sublime PHP_CodeSniffer plugin: 77 | - **ALT + S**: Runs PHPCS command for the open buffer. 78 | - **ALT + SHIFT + S**: Runs PHPCBF command for the open buffer. 79 | 80 | These commands are also available in Tools > PHP_CodeSniffer menu. 81 | -------------------------------------------------------------------------------- /STPluginReport.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) 7 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 8 | */ 9 | 10 | namespace PHP_CodeSniffer_SublimePlugin; 11 | 12 | use PHP_CodeSniffer\Files\File; 13 | use PHP_CodeSniffer\Reports\Report; 14 | 15 | class STPluginReport implements Report 16 | { 17 | 18 | 19 | /** 20 | * Generate a partial report for a single processed file. 21 | * 22 | * Function should return TRUE if it printed or stored data about the file 23 | * and FALSE if it ignored the file. Returning TRUE indicates that the file and 24 | * its data should be counted in the grand totals. 25 | * 26 | * @param array $report Prepared report data. 27 | * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. 28 | * @param bool $showSources Show sources? 29 | * @param int $width Maximum allowed line width. 30 | * 31 | * @return bool 32 | */ 33 | public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80) 34 | { 35 | if ($report['errors'] === 0 && $report['warnings'] === 0) { 36 | // Nothing to print. 37 | return false; 38 | } 39 | 40 | echo PHP_EOL; 41 | echo ' PHP_CodeSniffer found '.$report['errors'].' error'; 42 | if ($report['errors'] !== 1) { 43 | echo 's'; 44 | } 45 | 46 | if ($report['warnings'] > 0) { 47 | echo ' and '.$report['warnings'].' warning'; 48 | if ($report['warnings'] !== 1) { 49 | echo 's'; 50 | } 51 | } 52 | 53 | echo ' affecting '.count($report['messages']).' line'; 54 | if (count($report['messages']) !== 1) { 55 | echo 's'; 56 | } 57 | 58 | echo PHP_EOL; 59 | 60 | // Print the fixable message. 61 | if ($report['fixable'] > 0) { 62 | echo PHP_EOL; 63 | $message = 'Good news! '; 64 | if ($report['fixable'] > 1) { 65 | $message .= 'The '.$report['fixable'].' marked sniff violations'; 66 | } else { 67 | $message .= 'The marked sniff violation'; 68 | } 69 | 70 | $message .= ' below can be fixed automatically'; 71 | 72 | $length = (strlen($message) + 4); 73 | 74 | $fixButton = '[ Click here to fix this file ]'; 75 | $leftPadding = floor(($length - 4 - strlen($fixButton)) / 2); 76 | $rightPadding = ($length - $leftPadding - strlen($fixButton) - 4); 77 | $fixMessage = str_repeat(' ', $leftPadding).$fixButton.str_repeat(' ', $rightPadding); 78 | 79 | echo ' '.str_repeat('-', $length).PHP_EOL; 80 | echo ' | '.$message.' |'.PHP_EOL; 81 | echo ' | '.$fixMessage.' |'.PHP_EOL; 82 | echo ' '.str_repeat('-', $length).PHP_EOL; 83 | }//end if 84 | 85 | echo PHP_EOL.' Note: click a sniff violation below to jump directly to the relevant line'.PHP_EOL; 86 | 87 | // Work out the max line number for formatting. 88 | $maxLine = 0; 89 | foreach ($report['messages'] as $line => $lineErrors) { 90 | if ($line > $maxLine) { 91 | $maxLine = $line; 92 | } 93 | } 94 | 95 | $maxLineLength = strlen($maxLine); 96 | 97 | if ($report['errors'] > 0) { 98 | echo PHP_EOL.' Errors:'.PHP_EOL; 99 | foreach ($report['messages'] as $line => $lineErrors) { 100 | foreach ($lineErrors as $column => $colErrors) { 101 | foreach ($colErrors as $error) { 102 | if ($error['type'] !== 'ERROR') { 103 | continue; 104 | } 105 | 106 | $message = $error['message']; 107 | if ($showSources === true) { 108 | $message .= ' ('.$error['source'].')'; 109 | } 110 | 111 | echo ' '; 112 | if ($report['fixable'] > 0) { 113 | echo '['; 114 | if ($error['fixable'] === true) { 115 | echo 'x'; 116 | } else { 117 | echo ' '; 118 | } 119 | 120 | echo '] '; 121 | } 122 | 123 | $padding = ($maxLineLength - strlen($line)); 124 | echo 'Line '.$line.str_repeat(' ', $padding).': '.$message.PHP_EOL; 125 | }//end foreach 126 | }//end foreach 127 | }//end foreach 128 | }//end if 129 | 130 | if ($report['warnings'] > 0) { 131 | echo PHP_EOL.' Warnings:'.PHP_EOL; 132 | foreach ($report['messages'] as $line => $lineErrors) { 133 | foreach ($lineErrors as $column => $colErrors) { 134 | foreach ($colErrors as $error) { 135 | if ($error['type'] !== 'WARNING') { 136 | continue; 137 | } 138 | 139 | $message = $error['message']; 140 | if ($showSources === true) { 141 | $message .= ' ('.$error['source'].')'; 142 | } 143 | 144 | echo ' '; 145 | if ($report['fixable'] > 0) { 146 | echo '['; 147 | if ($error['fixable'] === true) { 148 | echo 'x'; 149 | } else { 150 | echo ' '; 151 | } 152 | 153 | echo '] '; 154 | } 155 | 156 | $padding = ($maxLineLength - strlen($line)); 157 | echo 'Line '.$line.str_repeat(' ', $padding).': '.$message.PHP_EOL; 158 | }//end foreach 159 | }//end foreach 160 | }//end foreach 161 | }//end if 162 | 163 | return true; 164 | 165 | }//end generateFileReport() 166 | 167 | 168 | /** 169 | * Prints all errors and warnings for each file processed. 170 | * 171 | * @param string $cachedData Any partial report data that was returned from 172 | * generateFileReport during the run. 173 | * @param int $totalFiles Total number of files processed during the run. 174 | * @param int $totalErrors Total number of errors found during the run. 175 | * @param int $totalWarnings Total number of warnings found during the run. 176 | * @param int $totalFixable Total number of problems that can be fixed. 177 | * @param bool $showSources Show sources? 178 | * @param int $width Maximum allowed line width. 179 | * @param bool $interactive Are we running in interactive mode? 180 | * @param bool $toScreen Is the report being printed to screen? 181 | * 182 | * @return void 183 | */ 184 | public function generate( 185 | $cachedData, 186 | $totalFiles, 187 | $totalErrors, 188 | $totalWarnings, 189 | $totalFixable, 190 | $showSources=false, 191 | $width=80, 192 | $interactive=false, 193 | $toScreen=true 194 | ) { 195 | echo $cachedData; 196 | 197 | }//end generate() 198 | 199 | 200 | }//end class 201 | 202 | -------------------------------------------------------------------------------- /icons/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squizlabs/sublime-PHP_CodeSniffer/e7fa40d3a7ad27553e8caf0014567cace82ed785/icons/error.png -------------------------------------------------------------------------------- /icons/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squizlabs/sublime-PHP_CodeSniffer/e7fa40d3a7ad27553e8caf0014567cace82ed785/icons/warning.png --------------------------------------------------------------------------------