├── .github └── workflows │ └── ci.yml ├── Changelog.md ├── Context.sublime-menu ├── Default (OSX).sublime-keymap ├── Default.sublime-commands ├── LICENSE.md ├── Main.sublime-menu ├── README.md ├── Side Bar.sublime-menu ├── example-settings ├── nix-all-commands.example ├── windows-7-phpcs-fixer-linter.example └── windows-8.1-phpcs-fixer-linter.example ├── phpcs.py ├── phpcs.sublime-settings └── tests └── test_phpcs.py /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | run-tests: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | st-version: [4] 11 | # st-version: [3, 4] 12 | # os: ["ubuntu-latest", "macOS-latest", "windows-latest"] 13 | os: ["ubuntu-latest"] 14 | runs-on: ${{ matrix.os }} 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: SublimeText/UnitTesting/actions/setup@v1 18 | with: 19 | # Details: https://github.com/SublimeText/UnitTesting/blob/master/actions/setup/action.yaml#L19 20 | package-name: Phpcs 21 | sublime-text-version: ${{ matrix.st-version }} 22 | - uses: SublimeText/UnitTesting/actions/run-tests@v1 -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## next 4 | 5 | ## 7.0.0 6 | 7 | - Removal of Scheck. 8 | - Refactor how we generate the phpcs executable 9 | - Add a test and run in GitHub Action 10 | - Parse the phpcs standards output a little better 11 | 12 | ## 6.9.0 13 | 14 | - Turn on tagging 15 | 16 | ## 6.8.0 17 | 18 | * Enhancement for allowing the configuration to use "~" which will be expanded out to your home directory. Thanks to [Quentin Dreyer](https://github.com/qkdreyer) for the [patch](https://github.com/benmatselby/sublime-phpcs/pull/169) 19 | 20 | 21 | ## 6.7.0 22 | 23 | * Enhancement for allowing the definition of a `phpcs.xml` in the root of the project. Thanks to [Jason Madrid](https://github.com/jasonmadrid) for the [patch](https://github.com/benmatselby/sublime-phpcs/pull/161) 24 | 25 | ## 6.6.3 26 | 27 | * Fix for [GH-100](https://github.com/benmatselby/sublime-phpcs/issues/100). Issues when opening Sublime Text when the last file was an image 28 | 29 | ## 6.6.2 30 | 31 | * Fix for [GH-155](https://github.com/benmatselby/sublime-phpcs/issues/155). --runtime-set not working correctly 32 | 33 | ## 6.6.1 34 | 35 | * Fix for [GH-129](https://github.com/benmatselby/sublime-phpcs/issues/129), which is the cbf not showing as enabled. 36 | 37 | ## 6.6.0 38 | 39 | * Support for PHP Code Beautifier and Fixer (phpcbf) [GH-127](https://github.com/benmatselby/sublime-phpcs/issues/127) 40 | 41 | ## 6.5.3 42 | 43 | * Fix for [GH-120](https://github.com/benmatselby/sublime-phpcs/issues/120). Thanks to [Hermann Herz](https://github.com/Heart1010) for raising this. 44 | 45 | ## 6.5.2 46 | 47 | * Fix for [GH-117](https://github.com/benmatselby/sublime-phpcs/issues/117). Thanks to [autumnlansing](https://github.com/autumnlansing) for raising. 48 | 49 | ## 6.5.1 50 | 51 | * Fix for [GH-113](https://github.com/benmatselby/sublime-phpcs/issues/113). Thanks to [Rafael Nascimento](https://github.com/Arcane07) for the patch. PHP-CS-Fixer not reporting outcome 52 | 53 | ## 6.5.0 54 | 55 | * Stylise the icon color [GH-114](https://github.com/benmatselby/sublime-phpcs/issues/114) 56 | 57 | ## 6.4.2 58 | 59 | * Bug fix for switching the coding standard when not using a project. 60 | * Set the default coding standard when the plugin is installed to PSR2. 61 | 62 | ## 6.4.1 63 | 64 | * Bug fix for switching the coding standard. It does not rely on php-cs-fixer path being set 65 | 66 | ## 6.4.0 67 | 68 | * Enhancement [GH-112](https://github.com/benmatselby/sublime-phpcs/issues/112). Ability to toggle the coding standard being used. 69 | 70 | ## 6.3.5 71 | 72 | * Enhancement: Always show php-cs-fixer support in context menu in side bar, just not enabled if php-cs-fixer is not configured [GH-103](https://github.com/benmatselby/sublime-phpcs/issues/103) 73 | 74 | ## 6.3.4 75 | 76 | * Fix for [GH-98](https://github.com/benmatselby/sublime-phpcs/issues/98) 77 | 78 | ## 6.3.3 79 | 80 | * Fix for [GH-93](https://github.com/benmatselby/sublime-phpcs/issues/93). If plugin is off, all sub processes should respect that. 81 | * Fix for [GH-94](https://github.com/benmatselby/sublime-phpcs/issues/94). Thanks to [WaveHack](https://github.com/WaveHack) for raising this and proposing a solution 82 | * Fix for [GH-91](https://github.com/benmatselby/sublime-phpcs/issues/91). Thanks to [Alex Russell](https://github.com/alexrussell) for proposing a solution 83 | * Fix for [GH-85](https://github.com/benmatselby/sublime-phpcs/issues/85). Thanks to [Peter Farsinsen](https://github.com/peterfarsinsen) for this patch 84 | 85 | ## 6.3.2 86 | 87 | * Minor enhancement to provide hint with configuration issue [GH-78](https://github.com/benmatselby/sublime-phpcs/issues/78) 88 | 89 | ## 6.3.1 90 | 91 | * Fix for ST2 which does not like trailing commas in the json. Fixes [GH-75](https://github.com/benmatselby/sublime-phpcs/pull/75) 92 | 93 | ## 6.3 94 | 95 | * Enhancement to toggle via the context menu or command palette if the plugin should execute on save or not [GH-73](https://github.com/benmatselby/sublime-phpcs/issues/73) 96 | * Enhancement to blacklist certain extensions if they form part of the main extensions to execute, the example being twig.php [GH-69](https://github.com/benmatselby/sublime-phpcs/issues/69) 97 | 98 | ## 6.2 99 | 100 | * Enhancement to support per project settings. Thanks to [Handrus Stephan Nogueira](https://github.com/handrus) for the contribution 101 | * Enhancement to reload the settings when changed, so you do not have to restart Sublime Text each time. 102 | 103 | ## 6.1 104 | 105 | * Bug fix for users of ST2 and PHP_CodeSniffer1.5.0 (Currently RC1). Essentially we now need to pass cwd so that PHP_CodeSniffer knows where to put the tmp files for its caching mechanism. Fixes [GH-68](https://github.com/benmatselby/sublime-phpcs/issues/68) 106 | 107 | ## 6.0.1 108 | 109 | * Missed a s/SCheck/scheck/ conversion 110 | 111 | ## 6.0 112 | 113 | * Add support for [scheck](https://github.com/facebook/pfff/wiki/Main). Thanks to [Darby Payne](https://github.com/dpayne) for this patch 114 | 115 | ## 5.1.1 116 | 117 | * Minor patch to scrub the last report so the points and line numbers are correct each time. Fixes GH-67 118 | 119 | ## 5.1 120 | 121 | * Only run commands if the _run preferences_ are true. Thanks to [Rys Sommefeldt](https://github.com/rys) for this patch. 122 | 123 | ## 5.0 124 | 125 | * Support for Sublime Text 3 [GH-60](https://github.com/benmatselby/sublime-phpcs/issues/60) 126 | 127 | ## 4.6.3 128 | 129 | * Patch to fix [GH-53](https://github.com/benmatselby/sublime-phpcs/issues/53) which was php-cs-fixer executing on save for non plugin based files (e.g. running for a python file). Thanks to [John Hoffmann](https://github.com/jhoffmann) for the solution. 130 | * Minor update to the README to cover off a gotcha on installing php-cs-fixer using Homebrew [GH-52](https://github.com/benmatselby/sublime-phpcs/issues/52) 131 | 132 | ## 4.6.2 133 | 134 | * Small patch to fix [GH-51](https://github.com/benmatselby/sublime-phpcs/issues/51). Thanks to [mstaatz](https://github.com/mstaatz) for raising the issue. 135 | 136 | ## 4.6.1 137 | 138 | * Provided configuration option "phpcs_commands_to_php_prefix" that allows you to distinguish which commands should have the php path prefixed. Thanks to [Hamrani ahmed](https://github.com/ahamrani) for raising [GH-49](https://github.com/benmatselby/sublime-phpcs/issues/49) 139 | 140 | ## 4.6 141 | 142 | * Fixes for windows based users and the use of phar files. Thanks to [Hamrani ahmed](https://github.com/ahamrani) for raising [GH-47](https://github.com/benmatselby/sublime-phpcs/issues/47) 143 | * Moved the options to the end of the php-cs-fixer command as per their documentation examples 144 | * Removed reloading settings code, as it seems redundant in latest build of Sublime Text 2 145 | 146 | ## 4.5.1 147 | 148 | * Minor changes to the README to better explain non package control installation. 149 | * Minor change to README for naming of the plugin 150 | * Minor change to when the "Goto Next Error" context menu is enabled 151 | 152 | ## 4.5 153 | 154 | * Define a setting "php_cs_fixer_show_quick_panel" that stops quick panel displaying for php-cs-fixer. Thanks to [Kevin Perrine](https://github.com/kevinsperrine/) for the pull request 155 | 156 | ## 4.4.1 157 | 158 | * Blank out the default setting for php_cs_fixer_executable_path. Thanks to [Eric Lewis](https://github.com/ericandrewlewis/) for the pull request. 159 | 160 | ## 4.4 161 | 162 | * Provide configuration options for each command to execute on save. Thanks to [Jeremy Romey](https://github.com/jeremyFreeAgent) for the [suggestion](https://github.com/benmatselby/sublime-phpcs/issues/36) 163 | 164 | ## 4.3 165 | 166 | * Ehancement for [GH-34](https://github.com/benmatselby/sublime-phpcs/issues/34) which provides a command to "Goto Next Error" which can also have a shortcut key assigned to it. Thanks to [Casey Becking](https://github.com/caseybecking) for raising the feature request 167 | * Ability to fun PHP-CS-Fixer on save now. Thanks to [Cedric Lombardot](https://github.com/cedriclombardot) for raising the feature request 168 | 169 | ## 4.2 170 | 171 | * Ability to configure if you want the errors to be highlighted in the editor. Thanks to [Aleksandr Gornostal](https://github.com/gornostal) 172 | 173 | ## 4.1.1 174 | 175 | * Small patch to be consistent with the naming of the tools. Thanks to [Beau Simensen](https://github.com/simensen) 176 | 177 | ## 4.1 178 | 179 | * Added support to use php-cs-fixer on a directory in the side bar 180 | * Now displays the php-cs-fixer changes in the quick panel 181 | 182 | ## 4.0 183 | 184 | * Added basic support for [php-cs-fixer](https://github.com/fabpot/PHP-CS-Fixer) based on the work by [Jeremy Romey](https://github.com/jeremyFreeAgent/sublime-php-cs-fixer/) 185 | 186 | ## 3.13 187 | 188 | * Setting to configure if phpcs is run when the plugin is invoked [GH-20](https://github.com/benmatselby/sublime-phpcs/issues/20). Thanks to [grEvenX](https://github.com/grEvenX) 189 | 190 | ## 3.12 191 | 192 | * Bug fix for [GH-18](https://github.com/benmatselby/sublime-phpcs/issues/18) which meant the plugin would fail to work when coming across non ascii characters 193 | * Added a show_debug setting for console output. Off by default 194 | 195 | ## 3.11 196 | 197 | * Support added to configure the php path [GH-16](https://github.com/benmatselby/sublime-phpcs/issues/16). Thanks to [Dan Previte](https://github.com/dprevite) 198 | * Added support for multiple file extensions, rather than using Sublimes syntax checker [GH-15](https://github.com/benmatselby/sublime-phpcs/issues/15) 199 | 200 | ## 3.10 201 | 202 | * Bug fix for [GH-13](https://github.com/benmatselby/sublime-phpcs/issues/13) which seems to be apparent when Sublime cannot find/load the settings file 203 | 204 | ## 3.9 205 | 206 | * Bug fix for [GH-12](https://github.com/benmatselby/sublime-phpcs/issues/12) 207 | * Bug fix for incorrectly mismatching sublime line numbers to line numbers from a report *if* there was only one error reported 208 | * Updated name of change log 209 | 210 | ## 3.8 211 | 212 | * Support added for running [phpmd](http://phpmd.org/) - Currently off by default 213 | 214 | ## 3.7 215 | 216 | * Updated Main.sublime-menu so we can change the key bindings from within the Preferences panel 217 | 218 | ## 3.6 219 | 220 | * Added the ability to show previous errors without generating the report again. Thanks to [Drarok](https://github.com/Drarok) [GH-9](https://github.com/benmatselby/sublime-phpcs/pull/9) 221 | * Put the processing back to being threaded which helps with large files. Thanks to [Drarok](https://github.com/Drarok) [GH-10](https://github.com/benmatselby/sublime-phpcs/pull/10) 222 | * Removed Side Bar Menu, as it doesn't make sense to run report against a non-open file 223 | * When selecting an error from the quick panel, also set the status bar message 224 | * Added "Show previous errors" to the context menu to be consistent 225 | * Added keymap for Mac OSX, cannot get the keys right when using Virtual Box to define for Windows and Linux, so leaving for the time being 226 | 227 | ## 3.5 228 | 229 | * Ability to show the errors in the status bar (configurable with on/off setting). Thanks to [Drarok](https://github.com/Drarok) 230 | * Bug fix with clearing the sniffer marks 231 | 232 | ## 3.4 233 | 234 | * Ability to not show the quick_panel errors on save, (errors shown by default). Thanks to [Drarok](https://github.com/Drarok) 235 | 236 | ## 3.3 237 | 238 | * Allow the user to specify the location of the phpcs application [GH-4](https://github.com/benmatselby/sublime-phpcs/issues/4) 239 | * Turned the linter checks on by default 240 | 241 | ## 3.2 242 | 243 | * Added the ability to run the PHP linter alongside the PHP_CodeSniffer 244 | * You can specify if you want the linter to run 245 | * You can specify the regex of the linter output, as it seems to differ on different systems 246 | 247 | ## 3.1 248 | 249 | * Updated the loading of settings to use static method 250 | * Updated the debugging calls to prefix with "Phpcs" 251 | * Added a counter of errors in the console log 252 | 253 | ## 3.0 254 | 255 | * Enables the plugin to work on the Windows platform [GH-1](https://github.com/benmatselby/sublime-phpcs/issues/1) 256 | * Removed some redundant code 257 | 258 | ## 2.0 259 | 260 | * Changed the way the checkstyle report was parsed. due to [GH-2](https://github.com/benmatselby/sublime-phpcs/issues/2) which means it should now work on Linux machines 261 | 262 | ## 1.1 263 | 264 | * Load the settings into memory if changed using _settings.add_on_change_. This only works on User settings rather than defaults, which is a limitation of the API from what I can see at the moment. 265 | * Define the coding standard as PEAR by default. 266 | * Introduced this Changelog.md file. 267 | * Raised [this feature request](http://sublimetext.userecho.com/topic/96221-gutter-hint-bubles-when-hovered-over/) so I can show tool tips in the gutter for each checkstyle error/warning 268 | 269 | ## 1.0 270 | 271 | * Initial release documented [here](http://soulbroken.co.uk/code/sublimephpcs) 272 | * Right click on a php file and generate checkstyle report in the gutter and/or quick panel 273 | * Settings to turn the reporting on/off for gutter and quick panel 274 | -------------------------------------------------------------------------------- /Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption": "-" }, 3 | { 4 | "caption": "PHP Code Sniffer", 5 | "children": 6 | [ 7 | { "command": "phpcs_sniff_this_file", "caption": "Sniff this file" }, 8 | { "command": "phpcs_show_previous_errors", "caption": "Show previous errors" }, 9 | { "command": "phpcs_goto_next_error", "caption": "Goto Next Error" }, 10 | { "command": "phpcs_clear_sniffer_marks", "caption": "Clear sniffer marks" }, 11 | { 12 | "caption": "Fix this file", 13 | "children": [ 14 | { 15 | "command": "phpcs_fix_this_file", 16 | "caption": "PHP-CS-Fixer (php-cs-fixer)", 17 | "args": { 18 | "tool": "Fixer" 19 | } 20 | }, 21 | { 22 | "command": "phpcs_fix_this_file", 23 | "caption": "PHP Code Beautifier (phpcbf)", 24 | "args": { 25 | "tool": "CodeBeautifier" 26 | } 27 | } 28 | ] 29 | }, 30 | { "command": "phpcs_toggle_plugin"}, 31 | { "command": "phpcs_switch_coding_standard", "caption": "Switch coding standard" } 32 | ] 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+super+s"], "command": "phpcs_show_previous_errors" }, 3 | { "keys": ["shift+ctrl+super+s"], "command": "phpcs_sniff_this_file" }, 4 | { "keys": ["ctrl+super+n"], "command": "phpcs_goto_next_error" } 5 | ] 6 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "PHP Code Sniffer: Sniff this file", 4 | "command": "phpcs_sniff_this_file" 5 | }, 6 | { 7 | "caption": "PHP Code Sniffer: Clear sniffer marks", 8 | "command": "phpcs_clear_sniffer_marks" 9 | }, 10 | { 11 | "caption": "PHP Code Sniffer: Show previous errors", 12 | "command": "phpcs_show_previous_errors" 13 | }, 14 | { 15 | "caption": "PHP Code Sniffer: Goto next error", 16 | "command": "phpcs_goto_next_error" 17 | }, 18 | { 19 | "caption": "PHP Coding Standards Fixer: Fix this file (PHP Code Beautifier)", 20 | "command": "phpcs_fix_this_file", 21 | "args": { 22 | "tool": "CodeBeautifier" 23 | } 24 | }, 25 | { 26 | "caption": "PHP Coding Standards Fixer: Fix this file (PHP-CS-Fixer)", 27 | "command": "phpcs_fix_this_file", 28 | "args": { 29 | "tool": "Fixer" 30 | } 31 | }, 32 | { 33 | "caption": "PHP Code Sniffer: Turn Execute On Save On", 34 | "command": "phpcs_toggle_plugin", 35 | "args": { 36 | "toggle": true 37 | } 38 | }, 39 | { 40 | "caption": "PHP Code Sniffer: Turn Execute On Save Off", 41 | "command": "phpcs_toggle_plugin", 42 | "args": { 43 | "toggle": false 44 | } 45 | }, 46 | { 47 | "caption": "PHP Code Sniffer: Switch coding standard", 48 | "command": "phpcs_switch_coding_standard" 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | New BSD License 2 | =============== 3 | 4 | Copyright (c) 2012, Ben Selby and contributors 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the names of the copyright holders nor the names of its 16 | contributors may be used to endorse or promote products derived from this 17 | software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "tools", 4 | "caption": "Tools", 5 | "children": 6 | [ 7 | { 8 | "caption": "PHP Code Sniffer...", 9 | "command": "show_overlay", 10 | "args": {"overlay": "command_palette", "text": "PHP Code Sniffer: " } 11 | } 12 | ] 13 | }, 14 | { 15 | "caption": "Preferences", 16 | "mnemonic": "n", 17 | "id": "preferences", 18 | "children": 19 | [ 20 | { 21 | "caption": "Package Settings", 22 | "mnemonic": "P", 23 | "id": "package-settings", 24 | "children": 25 | [ 26 | { 27 | "caption": "PHP Code Sniffer", 28 | "children": 29 | [ 30 | { 31 | "command": "open_file", 32 | "args": {"file": "${packages}/Phpcs/phpcs.sublime-settings"}, 33 | "caption": "Settings – Default" 34 | }, 35 | { 36 | "command": "open_file", 37 | "args": {"file": "${packages}/User/phpcs.sublime-settings"}, 38 | "caption": "Settings – User" 39 | }, 40 | { "caption": "-" }, 41 | { 42 | "command": "open_file", 43 | "args": { 44 | "file": "${packages}/Phpcs/Default (OSX).sublime-keymap", 45 | "platform": "OSX" 46 | }, 47 | "caption": "Key Bindings – Default" 48 | }, 49 | { 50 | "command": "open_file", 51 | "args": { 52 | "file": "${packages}/Phpcs/Default (Linux).sublime-keymap", 53 | "platform": "Linux" 54 | }, 55 | "caption": "Key Bindings – Default" 56 | }, 57 | { 58 | "command": "open_file", 59 | "args": { 60 | "file": "${packages}/Phpcs/Default (Windows).sublime-keymap", 61 | "platform": "Windows" 62 | }, 63 | "caption": "Key Bindings – Default" 64 | }, 65 | { 66 | "command": "open_file", 67 | "args": { 68 | "file": "${packages}/User/Default (OSX).sublime-keymap", 69 | "platform": "OSX" 70 | }, 71 | "caption": "Key Bindings – User" 72 | }, 73 | { 74 | "command": "open_file", 75 | "args": { 76 | "file": "${packages}/User/Default (Linux).sublime-keymap", 77 | "platform": "Linux" 78 | }, 79 | "caption": "Key Bindings – User" 80 | }, 81 | { 82 | "command": "open_file", 83 | "args": { 84 | "file": "${packages}/User/Default (Windows).sublime-keymap", 85 | "platform": "Windows" 86 | }, 87 | "caption": "Key Bindings – User" 88 | }, 89 | { "caption": "-" } 90 | ] 91 | } 92 | ] 93 | } 94 | ] 95 | } 96 | ] 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sublime-phpcs 2 | 3 | This is a plugin for Sublime Text which provides checkstyle reports using the following tools (all optional): 4 | 5 | - PHP_CodeSniffer (`phpcs`) 6 | - Linter (`php -l`) 7 | - PHP Mess Detector (`phpmd`) 8 | 9 | You can also configure the plugin to fix the issues using either 10 | 11 | - PHP Coding Standards Fixer (php-cs-fixer) 12 | - PHP Code Beautifier (phpcbf) application 13 | 14 | ## Requirements 15 | 16 | Requirements for this plugin, should you want all the options to work: 17 | 18 | - [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) 3.5+ 19 | - [PHPMD](https://phpmd.org) 2.8+ 20 | - [PHP CS Fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer) 2.6+ 21 | 22 | This plugin has been tested on: 23 | 24 | - Mac OS X 10.8.2 25 | - Ubuntu 11.10 26 | - Windows 7 27 | - Sublime Text 2 28 | - Sublime Text 3 29 | - Sublime Text 4 30 | 31 | It may work with other versions, but we cannot confirm that. 32 | 33 | ## Installation 34 | 35 | Use Sublime Text’s Package Control (Tools -> Command Palette -> Package Control: Install Package -> Phpcs) to install this plugin. This is the recommended installation path. 36 | 37 | Or 38 | 39 | Simply checkout the git repo into “~/Library/Application Support/Sublime Text/Packages/ or the equivalent folder on Windows or Linux. 40 | 41 | ```shell 42 | cd ~/Library/Application\ Support/Sublime\ Text/Packages/ 43 | git clone git://github.com/benmatselby/sublime-phpcs.git Phpcs 44 | ``` 45 | 46 | In both cases, you may need to then configure the following with the actual path to the application: 47 | 48 | - “phpcs_php_path” 49 | - “phpcs_executable_path” 50 | - “phpmd_executable_path” 51 | - “php_cs_fixer_executable_path” 52 | 53 | They are optional for the plugin. The path needs to include the application such as `/usr/local/bin/phpcs`. 54 | 55 | In order to get the path of the application (On a Mac/Linux based environment), you can use: 56 | 57 | ```shell 58 | which phpcs 59 | which phpmd 60 | which php-cs-fixer 61 | which phpcbf 62 | ``` 63 | 64 | ## Features 65 | 66 | - Ability to run PHP_CodeSniffer 67 | - Ability to run php -l on the open file 68 | - Ability to run PHP Mess Detector on the open file 69 | - Show cached results from PHP_CodeSniffer in open file 70 | - Show errors in the Quick Panel 71 | - Show errors in the Gutter 72 | - Highlight the errors in the editor 73 | - Show the error for a given line in the status bar 74 | - Ability to specify the regular expression of the linter errors 75 | - Ability to specify the location of the PHP_CodeSniffer application 76 | - Ability to specify the location of the PHP Mess Detector application 77 | - Ability to run the PHP Coding Standards Fixer tool which fixes most issues in your code when you want to follow the PHP coding standards as defined in the PSR-1 and PSR-2 documents 78 | - Ability to run the PHP Code Beautifier tool which fixes most issues in your code when you want to follow PHP coding standards 79 | 80 | Once you have right clicked on a file and selected “PHP CodeSniffer” > “Sniff this file…” you will get the output as shown below (depending on the settings you have defined): 81 | 82 | ## Configuration 83 | 84 | You can also define the configuration for the following settings, be it for a project, user settings or the default settings: 85 | 86 | ### Plugin 87 | 88 | - `show_debug` – Do you want the debug information to be sent to the console? 89 | - `extensions_to_execute` – Which filetypes do you want the plugin to execute for? 90 | - `extensions_to_blacklist` – Override the extensions_to_execute in case you have a sub extension such as twig.php etc. 91 | - `phpcs_execute_on_save` – Do you want the code sniffer plugin to run on file save for php files? 92 | - `phpcs_show_errors_on_save` – Do you want the errors to be displayed in quick_panel on save? 93 | - `phpcs_show_gutter_marks` – Do you want the errors to be displayed in the gutter? 94 | - `phpcs_outline_for_errors` – Do you want the errors to be highlighted in the editor? 95 | - `phpcs_show_errors_in_status` – Do you want the errors to be displayed in status bar when clicking on the line with error? 96 | - `phpcs_show_quick_panel` – Do you want the errors to be displayed in the quick panel? 97 | - `phpcs_php_prefix_path` – Needed on windows for phar based applications. Also if you cannot make phar executable. Avoid if possible 98 | - `phpcs_commands_to_php_prefix` – List of commands you want the php path to prefix. This would be useful, if you have some commands as a phar that cannot be run without the php prefix, and others using native command. 99 | - `phpcs_icon_scope_color` - What colour to stylise the icon. This needs knowledge of theming of Sublime Test, as it uses scope colours from the theme to “tint” the dot icon. See here 100 | 101 | ### PHP_CodeSniffer 102 | 103 | - `phpcs_sniffer_run` – Do you want the PHPCS checker to run? 104 | - `phpcs_command_on_save` – Do you want the command to execute on save? 105 | - `phpcs_executable_path` – The path to the phpcs executable. If empty string, use PATH to find it 106 | - `phpcs_additional_args` – This is the extra information you want to pass to the phpcs command. For example which “standard” you want to run, and if you want to show warnings or not 107 | 108 | ### PHP CodeSniffer Fixer 109 | 110 | - `php_cs_fixer_on_save` – Do you want to run the fixer on file save? 111 | - `php_cs_fixer_show_quick_panel` – Do you want the quick panel to display on execution? 112 | - `php_cs_fixer_executable_path` – The path to the php-cs-fixer application. 113 | - `php_cs_fixer_additional_args` – This is the extra information you want to pass to the php-cs-fixer command. For example which “fixers” you want to run 114 | 115 | ### PHP Code Beautifier 116 | 117 | - `phpcbf_on_save` – Do you want to run the fixer on file save? 118 | - `phpcbf_show_quick_panel` – Do you want the quick panel to display on execution? 119 | - `phpcbf_executable_path` – The path to the phpcbf application. 120 | - `phpcbf_additional_args` – This is the extra information you want to pass to the phpcbf command. For example which “standard” to use in order to fix the issues 121 | 122 | ### PHP Linter 123 | 124 | - `phpcs_linter_run` – Do you want the PHP linter to run? 125 | - `phpcs_linter_command_on_save` – Do you want the command to execute on save? 126 | - `phpcs_php_path` – The path to the PHP executable. If empty string, use PATH to find it 127 | - `phpcs_linter_regex` – The regex for the PHP linter output 128 | 129 | ### PHP Mess Detector 130 | 131 | - `phpmd_run` – Do you want the PHPMD to run? Off by default 132 | - `phpmd_command_on_save` – Do you want the command to execute on save? 133 | - `phpmd_executable_path` – The path to the phpmd executable. If empty string, use PATH to find it 134 | - `phpmd_additional_args` – This is the extra information you want to pass to the phpmd command. For example which “rulesets” you want to run 135 | 136 | ### Project Based Settings 137 | 138 | Your .project file should look something like this: 139 | 140 | ```json 141 | { 142 | "folders": [{}], 143 | "settings": { 144 | "phpcs": { 145 | "phpcs_additional_args": { 146 | "--standard": "/path/to/.composer/vendor/drupal/coder/coder_sniffer/Drupal" 147 | } 148 | } 149 | } 150 | } 151 | ``` 152 | 153 | Of course this is a example to apply Drupal code sniffer. This could be anything. Whatever you can have on this package settings it can be overwritten under the settings -> phpcs 154 | 155 | ## FAQ 156 | 157 | ### What do I do when I get "OSError: [Errno 8] Exec format error"? 158 | 159 | - This seems to be an issue you may get with regards to wrapper scripts. 160 | - Please make sure that the application/script you are referencing has the correct shebang line, as per [GH-79](https://github.com/benmatselby/sublime-phpcs/issues/79) 161 | 162 | ### What do I do when I get "OSError: [Error 2] No such file or directory"? 163 | 164 | - Well, first of all you need to check that you have PHP_CodeSniffer, and if being used, the phpmd application. 165 | - If you have these applications installed, then it sounds like those applications are not in your PATH, or cannot be found in your PATH by the Python runtime, so configure "phpcs_php_path", "phpcs_executable_path", "phpmd_executable_path" and "php_cs_fixer_executable_path" with the actual paths to those applications 166 | 167 | ### What do I do when I get "OSError: [Errno 13] Permission denied"? 168 | 169 | - It sounds like your path settings are incorrect. 170 | - You need to make sure that when you specifiy the path you include the entire path including the application 171 | 172 | ```shell 173 | $ which phpcs 174 | /usr/local/bin/phpcs 175 | ``` 176 | 177 | - That entire output is the path you need in your configs. 178 | 179 | ### What if I've installed the applications using Homebrew? 180 | 181 | If you have installed php-cs-fixer, phpmd or phpcs via homebrew then please make sure that you define the "_\_executable_path_" option to the .phar application and not the wrapper script that is placed in your bin folder, as this will cause odd behaviour. 182 | 183 | ### What other Key Bindings can I setup? 184 | 185 | The following is a list of commands that you can bind to a keyboard shortcut: 186 | 187 | - phpcs_fix_this_file 188 | - phpcs_clear_sniffer_marks 189 | - phpcs_goto_next_error 190 | - phpcs_show_previous_errors 191 | - phpcs_sniff_this_file 192 | 193 | In order to achieve this you need to add the following to one of your key bindings settings file: 194 | 195 | ```json 196 | { "keys": ["ctrl+super+t"], "command": "phpcs_clear_sniffer_marks" } 197 | ``` 198 | 199 | To decide which "Fixer" to use, you can do: 200 | 201 | ```json 202 | { "keys": ["super+k", "super+f"], "command": "phpcs_fix_this_file", "args": {"tool": "CodeBeautifier"}}, 203 | ``` 204 | 205 | or 206 | 207 | ```json 208 | { "keys": ["super+k", "super+f"], "command": "phpcs_fix_this_file", "args": {"tool": "Fixer"}}, 209 | ``` 210 | 211 | You can then change the ctrl+super+t combination to something of your choosing. 212 | -------------------------------------------------------------------------------- /Side Bar.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption": "-" }, 3 | { 4 | "caption": "PHP Code Sniffer", 5 | "children": 6 | [ 7 | { 8 | "caption": "Fix this file", 9 | "children": [ 10 | { 11 | "command": "phpcs_fix_this_directory", 12 | "caption": "PHP-CS-Fixer (php-cs-fixer)", 13 | "args": { 14 | "paths": [], 15 | "tool": "Fixer" 16 | } 17 | }, 18 | { 19 | "command": "phpcs_fix_this_directory", 20 | "caption": "PHP Code Beautifier (phpcbf)", 21 | "args": { 22 | "paths": [], 23 | "tool": "CodeBeautifier" 24 | } 25 | } 26 | ] 27 | }, 28 | ] 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /example-settings/nix-all-commands.example: -------------------------------------------------------------------------------- 1 | { 2 | // *nix based systems, Mac OS X and Linux 3 | // - All commands on and using PATHS 4 | 5 | // We want debugging on 6 | "show_debug": true, 7 | 8 | // Only execute for .php files 9 | "extensions_to_execute": ["php"], 10 | 11 | // Do not execute for twig files 12 | "extensions_to_blacklist": ["twig.php"], 13 | 14 | // Execute the sniffer on file save 15 | "phpcs_execute_on_save": false, 16 | 17 | // Show the error list after save. 18 | "phpcs_show_errors_on_save": true, 19 | 20 | // Show the errors in the gutter 21 | "phpcs_show_gutter_marks": true, 22 | 23 | // Show outline for errors 24 | "phpcs_outline_for_errors": true, 25 | 26 | // Show the errors in the status bar 27 | "phpcs_show_errors_in_status": true, 28 | 29 | // Show the errors in the quick panel so you can then goto line 30 | "phpcs_show_quick_panel": true, 31 | 32 | 33 | // PHP_CodeSniffer settings 34 | // Run the PHP_CodeSniffer 35 | "phpcs_sniffer_run": true, 36 | 37 | // Execute PHP_CodeSniffer on save 38 | "phpcs_command_on_save": true, 39 | 40 | // Path to phpcs 41 | "phpcs_executable_path": "/usr/local/bin/phpcs", 42 | 43 | // Run the PEAR standard without warnings 44 | "phpcs_additional_args": { 45 | "--standard": "PEAR", 46 | "-n": "" 47 | }, 48 | 49 | 50 | // PHP-CS-Fixer settings 51 | // Do not automatically fix the errors 52 | "php_cs_fixer_on_save": false, 53 | 54 | // Show the fixes in the quick panel 55 | "php_cs_fixer_show_quick_panel": true, 56 | 57 | // Path to where php-cs-fixer.phar is 58 | "php_cs_fixer_executable_path": "/usr/local/bin/php-cs-fixer", 59 | 60 | // Run all levels of fixing 61 | "php_cs_fixer_additional_args": { 62 | }, 63 | 64 | 65 | // PHP Linter settings 66 | // Lint each file 67 | "phpcs_linter_run": true, 68 | 69 | // Execute the linter on save 70 | "phpcs_linter_command_on_save": true, 71 | 72 | // Use the $PATH version of php 73 | "phpcs_php_path": "", 74 | 75 | // Regex for the errors the linter produces 76 | "phpcs_linter_regex": "(?P.*) on line (?P\\d+)", 77 | 78 | 79 | // PHP Mess Detector settings 80 | // Execute phpmd 81 | "phpmd_run": true, 82 | 83 | // Execute the phpmd on file save 84 | "phpmd_command_on_save": true, 85 | 86 | // Path to where the phpmd application is 87 | "phpmd_executable_path": "/usr/local/bin/phpmd", 88 | 89 | // What args I want to pass to phpmd 90 | "phpmd_additional_args": { 91 | "codesize,unusedcode,naming": "" 92 | }, 93 | } 94 | -------------------------------------------------------------------------------- /example-settings/windows-7-phpcs-fixer-linter.example: -------------------------------------------------------------------------------- 1 | { 2 | // Example for: 3 | // - Windows 7 4 | // - With phpcs and php-cs-fixer support 5 | 6 | // We want debugging on 7 | "show_debug": true, 8 | 9 | // Only execute the plugin for php files 10 | "extensions_to_execute": ["php"], 11 | 12 | // Do not execute for twig files 13 | "extensions_to_blacklist": ["twig.php"], 14 | 15 | // Execute the sniffer on file save 16 | "phpcs_execute_on_save": true, 17 | 18 | // Show the error list after save. 19 | "phpcs_show_errors_on_save": true, 20 | 21 | // Show the errors in the gutter 22 | "phpcs_show_gutter_marks": true, 23 | 24 | // Show outline for errors 25 | "phpcs_outline_for_errors": true, 26 | 27 | // Show the errors in the status bar 28 | "phpcs_show_errors_in_status": true, 29 | 30 | // Show the errors in the quick panel so you can then goto line 31 | "phpcs_show_quick_panel": true, 32 | 33 | // Path to php on windows installation 34 | // This is needed as we cannot run phars on windows, so we run it through php 35 | "phpcs_php_prefix_path": "C:\\Program Files (x86)\\PHP\\php.exe", 36 | 37 | // We want the fixer to be run through the php application 38 | "phpcs_commands_to_php_prefix": ["Fixer"], 39 | 40 | 41 | // PHP_CodeSniffer settings 42 | // Yes, run the phpcs command 43 | "phpcs_sniffer_run": true, 44 | 45 | // And execute it on save 46 | "phpcs_command_on_save": true, 47 | 48 | // This is the path to the bat file when we installed PHP_CodeSniffer 49 | "phpcs_executable_path": "C:\\Program Files (x86)\\PHP\\PEAR\\phpcs.bat", 50 | 51 | // I want to run the PSR2 standard, and ignore warnings 52 | "phpcs_additional_args": { 53 | "--standard": "PSR2", 54 | "-n": "" 55 | }, 56 | 57 | 58 | // PHP-CS-Fixer settings 59 | // Don't want to auto fix issue with php-cs-fixer 60 | "php_cs_fixer_on_save": false, 61 | 62 | // Show the quick panel 63 | "php_cs_fixer_show_quick_panel": true, 64 | 65 | // The fixer phar file is stored here: 66 | "php_cs_fixer_executable_path": "C:\\Program Files (x86)\\PHP\\PEAR\\php-cs-fixer.phar", 67 | 68 | // Additional arguments, run all levels of fixing 69 | "php_cs_fixer_additional_args": { 70 | }, 71 | 72 | 73 | // PHP Linter settings 74 | // Yes, lets lint the files 75 | "phpcs_linter_run": true, 76 | 77 | // And execute that on each file when saved (php only as per extensions_to_execute) 78 | "phpcs_linter_command_on_save": true, 79 | 80 | // Path to php 81 | "phpcs_php_path": "C:\\Program Files (x86)\\PHP\\php.exe", 82 | 83 | // This is the regex format of the errors 84 | "phpcs_linter_regex": "(?P.*) on line (?P\\d+)", 85 | 86 | 87 | // PHP Mess Detector settings 88 | // Not turning on the mess detector here 89 | "phpmd_run": false, 90 | "phpmd_command_on_save": false, 91 | "phpmd_executable_path": "", 92 | "phpmd_additional_args": { 93 | "codesize,naming,unusedcode": "" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /example-settings/windows-8.1-phpcs-fixer-linter.example: -------------------------------------------------------------------------------- 1 | { 2 | // Example for: 3 | // - Windows 8.1 4 | // - With phpcs and php-cs-fixer support 5 | // - You have to change "YOUR_USERNAME_HERE" strings. 6 | // - Notice: This uses phpcs which is installed 7 | // - using composer not xampp. 8 | // - Be sure to install phpcs using composer. 9 | 10 | // We want debugging on 11 | "show_debug": true, 12 | 13 | // Only execute the plugin for php files 14 | "extensions_to_execute": ["php"], 15 | 16 | // Do not execute for twig files 17 | "extensions_to_blacklist": ["twig.php"], 18 | 19 | // Execute the sniffer on file save 20 | "phpcs_execute_on_save": true, 21 | 22 | // Show the error list after save. 23 | "phpcs_show_errors_on_save": true, 24 | 25 | // Show the errors in the gutter 26 | "phpcs_show_gutter_marks": true, 27 | 28 | // Show outline for errors 29 | "phpcs_outline_for_errors": true, 30 | 31 | // Show the errors in the status bar 32 | "phpcs_show_errors_in_status": true, 33 | 34 | // Show the errors in the quick panel so you can then goto line 35 | "phpcs_show_quick_panel": true, 36 | 37 | // Path to php on windows installation 38 | // This is needed as we cannot run phars on windows, so we run it through php 39 | "phpcs_php_prefix_path": "", 40 | 41 | // We want the fixer to be run through the php application 42 | "phpcs_commands_to_php_prefix": ["Fixer"], 43 | 44 | 45 | // PHP_CodeSniffer settings 46 | // Yes, run the phpcs command 47 | "phpcs_sniffer_run": true, 48 | 49 | // And execute it on save 50 | "phpcs_command_on_save": true, 51 | 52 | // This is the path to the bat file when we installed PHP_CodeSniffer 53 | "phpcs_executable_path": "C:\\Users\\YOUR_USERNAME_HERE\\AppData\\Roaming\\Composer\\vendor\\bin\\phpcs.bat", 54 | 55 | // I want to run the PSR2 standard, and ignore warnings 56 | "phpcs_additional_args": { 57 | "--standard": "PSR2", 58 | "-n": "" 59 | }, 60 | 61 | 62 | // PHP-CS-Fixer settings 63 | // Don't want to auto fix issue with php-cs-fixer 64 | "php_cs_fixer_on_save": false, 65 | 66 | // Show the quick panel 67 | "php_cs_fixer_show_quick_panel": true, 68 | 69 | // The fixer phar file is stored here: 70 | "php_cs_fixer_executable_path": "C:\\Users\\YOUR_USERNAME_HERE\\AppData\\Roaming\\Composer\\vendor\\bin\\php-cs-fixer.bat", 71 | 72 | // Additional arguments, run all levels of fixing 73 | "php_cs_fixer_additional_args": { 74 | }, 75 | 76 | 77 | // PHP Linter settings 78 | // Yes, lets lint the files 79 | "phpcs_linter_run": true, 80 | 81 | // And execute that on each file when saved (php only as per extensions_to_execute) 82 | "phpcs_linter_command_on_save": true, 83 | 84 | // Path to php 85 | "phpcs_php_path": "C:\\xampp\\php\\php.exe", 86 | 87 | // This is the regex format of the errors 88 | "phpcs_linter_regex": "(?P.*) on line (?P\\d+)", 89 | 90 | 91 | // PHP Mess Detector settings 92 | // Not turning on the mess detector here 93 | "phpmd_run": false, 94 | "phpmd_command_on_save": false, 95 | "phpmd_executable_path": "", 96 | "phpmd_additional_args": {} 97 | } 98 | -------------------------------------------------------------------------------- /phpcs.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | import re 4 | import subprocess 5 | import threading 6 | import time 7 | import sublime 8 | import sublime_plugin 9 | import sys 10 | 11 | try: 12 | from HTMLParser import HTMLParser 13 | except: 14 | from html.parser import HTMLParser 15 | from os.path import expanduser 16 | 17 | 18 | class Pref: 19 | project_file = None 20 | 21 | keys = [ 22 | "show_debug", 23 | "extensions_to_execute", 24 | "extensions_to_blacklist", 25 | "phpcs_execute_on_save", 26 | "phpcs_show_errors_on_save", 27 | "phpcs_show_gutter_marks", 28 | "phpcs_outline_for_errors", 29 | "phpcs_show_errors_in_status", 30 | "phpcs_show_quick_panel", 31 | "phpcs_php_prefix_path", 32 | "phpcs_commands_to_php_prefix", 33 | "phpcs_icon_scope_color", 34 | "phpcs_sniffer_run", 35 | "phpcs_command_on_save", 36 | "phpcs_executable_path", 37 | "phpcs_additional_args", 38 | "php_cs_fixer_on_save", 39 | "php_cs_fixer_show_quick_panel", 40 | "php_cs_fixer_executable_path", 41 | "php_cs_fixer_additional_args", 42 | "phpcbf_on_save", 43 | "phpcbf_show_quick_panel", 44 | "phpcbf_executable_path", 45 | "phpcbf_additional_args", 46 | "phpcs_linter_run", 47 | "phpcs_linter_command_on_save", 48 | "phpcs_php_path", 49 | "phpcs_linter_regex", 50 | "phpmd_run", 51 | "phpmd_command_on_save", 52 | "phpmd_executable_path", 53 | "phpmd_additional_args", 54 | ] 55 | 56 | def load(self): 57 | self.settings = sublime.load_settings("phpcs.sublime-settings") 58 | 59 | if ( 60 | sublime.active_window() is not None 61 | and sublime.active_window().active_view() is not None 62 | ): 63 | project_settings = sublime.active_window().active_view().settings() 64 | if project_settings.has("phpcs"): 65 | project_settings.clear_on_change("phpcs") 66 | self.project_settings = project_settings.get("phpcs") 67 | project_settings.add_on_change("phpcs", pref.load) 68 | else: 69 | self.project_settings = {} 70 | else: 71 | self.project_settings = {} 72 | 73 | for key in self.keys: 74 | self.settings.clear_on_change(key) 75 | setattr(self, key, self.get(key)) 76 | self.settings.add_on_change(key, pref.load) 77 | 78 | def get(self, key): 79 | if key in self.project_settings: 80 | return self.project_settings.get(key) 81 | else: 82 | return self.settings.get(key) 83 | 84 | def set(self, key, value): 85 | if key in self.project_settings: 86 | self.project_settings[key] = value 87 | else: 88 | self.settings.set(key, value) 89 | 90 | 91 | pref = Pref() 92 | 93 | st_version = 2 94 | if sublime.version() == "" or int(sublime.version()) > 3000: 95 | st_version = 3 96 | 97 | if st_version == 2: 98 | pref.load() 99 | 100 | 101 | def plugin_loaded(): 102 | pref.load() 103 | 104 | 105 | def debug_message(msg): 106 | if pref.get("show_debug") == True: 107 | print("[Phpcs] " + str(msg)) 108 | 109 | 110 | class CheckstyleError: 111 | """Represents an error that needs to be displayed on the UI for the user""" 112 | 113 | def __init__(self, line, message): 114 | self.line = line 115 | self.message = message 116 | 117 | def get_line(self): 118 | return self.line 119 | 120 | def get_message(self): 121 | data = self.message 122 | 123 | if st_version == 3: 124 | return HTMLParser().unescape(data) 125 | else: 126 | try: 127 | data = data.decode("utf-8") 128 | except UnicodeDecodeError: 129 | data = data.decode( 130 | sublime.active_window() 131 | .active_view() 132 | .settings() 133 | .get("fallback_encoding") 134 | ) 135 | return HTMLParser().unescape(data) 136 | 137 | def set_point(self, point): 138 | self.point = point 139 | 140 | def get_point(self): 141 | return self.point 142 | 143 | 144 | class ShellCommand: 145 | """Base class for shelling out a command to the terminal""" 146 | 147 | def __init__(self): 148 | self.error_list = [] 149 | 150 | # Default the working directory for the shell command to the user's home dir. 151 | self.workingDir = expanduser("~") 152 | 153 | def setWorkingDir(self, dir): 154 | self.workingDir = dir 155 | 156 | def get_errors(self, path): 157 | self.execute(path) 158 | return self.error_list 159 | 160 | def shell_out(self, cmd): 161 | data = None 162 | 163 | for i, arg in enumerate(cmd): 164 | if isinstance(arg, str) and arg.startswith("~"): 165 | cmd[i] = os.path.expanduser(arg) 166 | 167 | if st_version == 3: 168 | debug_message(" ".join(cmd)) 169 | else: 170 | for index, arg in enumerate(cmd[:]): 171 | cmd[index] = arg.encode(sys.getfilesystemencoding()) 172 | 173 | debug_message(" ".join(cmd)) 174 | 175 | debug_message(" ".join(cmd)) 176 | 177 | info = None 178 | if os.name == "nt": 179 | info = subprocess.STARTUPINFO() 180 | info.dwFlags |= subprocess.STARTF_USESHOWWINDOW 181 | info.wShowWindow = subprocess.SW_HIDE 182 | 183 | debug_message("cwd: " + self.workingDir) 184 | proc = subprocess.Popen( 185 | cmd, 186 | stdin=subprocess.PIPE, 187 | stdout=subprocess.PIPE, 188 | stderr=subprocess.STDOUT, 189 | startupinfo=info, 190 | cwd=self.workingDir, 191 | ) 192 | 193 | if proc.stdout: 194 | data = proc.communicate()[0] 195 | 196 | if st_version == 3: 197 | return data.decode() 198 | else: 199 | return data 200 | 201 | def execute(self, path): 202 | debug_message("Command not implemented") 203 | 204 | 205 | class Sniffer(ShellCommand): 206 | """Concrete class for PHP_CodeSniffer""" 207 | 208 | def execute(self, path): 209 | if pref.get("phpcs_sniffer_run") != True: 210 | return 211 | 212 | args = self.get_executable_args() 213 | args.append("--report=checkstyle") 214 | 215 | # Add the additional arguments from the settings file to the command 216 | for key, value in pref.get("phpcs_additional_args").items(): 217 | arg = key 218 | if key == "--runtime-set": 219 | args.append(arg) 220 | args.append(value) 221 | elif value != "": 222 | arg += "=" + value 223 | args.append(arg) 224 | 225 | target = os.path.normpath(path) 226 | 227 | # Set the working directory for the command to the path of the target file, allowing 228 | # phpcs the opportunity to find a default configuration file (phpcs.xml) in the file's path. 229 | self.setWorkingDir(os.path.dirname(target)) 230 | 231 | args.append(target) 232 | self.parse_report(args) 233 | 234 | def get_executable_args(self): 235 | """ 236 | Figure out how we are going to construct the executable path for phpcs 237 | """ 238 | args = [] 239 | 240 | if pref.get( 241 | "phpcs_php_prefix_path" 242 | ) != "" and self.__class__.__name__ in pref.get("phpcs_commands_to_php_prefix"): 243 | args = [pref.get("phpcs_php_prefix_path")] 244 | 245 | if pref.phpcs_executable_path != "": 246 | application_path = pref.get("phpcs_executable_path") 247 | else: 248 | application_path = "phpcs" 249 | 250 | if len(args) > 0: 251 | args.append(application_path) 252 | else: 253 | args = [application_path] 254 | 255 | return args 256 | 257 | def parse_report(self, args): 258 | report = self.shell_out(args) 259 | debug_message(report) 260 | lines = re.finditer( 261 | '.*line="(?P\d+)" column="(?P\d+)" severity="(?P\w+)" message="(?P.*)" source', 262 | report, 263 | ) 264 | 265 | for line in lines: 266 | error = CheckstyleError(line.group("line"), line.group("message")) 267 | self.error_list.append(error) 268 | 269 | def get_standards_available(self): 270 | args = self.get_executable_args() 271 | args.append("-i") 272 | 273 | output = self.shell_out(args) 274 | standards = output[35:].replace(" and ", ", ").strip().split(", ") 275 | return standards 276 | 277 | 278 | class Fixer(ShellCommand): 279 | """Concrete class for PHP-CS-Fixer""" 280 | 281 | def execute(self, path): 282 | args = [] 283 | 284 | if pref.get( 285 | "phpcs_php_prefix_path" 286 | ) != "" and self.__class__.__name__ in pref.get("phpcs_commands_to_php_prefix"): 287 | args = [pref.get("phpcs_php_prefix_path")] 288 | 289 | if pref.get("php_cs_fixer_executable_path") != "": 290 | if len(args) > 0: 291 | args.append(pref.get("php_cs_fixer_executable_path")) 292 | else: 293 | args = [pref.get("php_cs_fixer_executable_path")] 294 | else: 295 | debug_message( 296 | "php_cs_fixer_executable_path is not set, therefore cannot execute" 297 | ) 298 | sublime.error_message( 299 | 'The "php_cs_fixer_executable_path" is not set, therefore cannot execute this command' 300 | ) 301 | return 302 | 303 | args.append("fix") 304 | args.append(os.path.normpath(path)) 305 | args.append("--verbose") 306 | 307 | # Add the additional arguments from the settings file to the command 308 | for key, value in pref.get("php_cs_fixer_additional_args").items(): 309 | arg = key 310 | if value != "": 311 | arg += "=" + value 312 | args.append(arg) 313 | 314 | self.parse_report(args) 315 | 316 | def parse_report(self, args): 317 | report = self.shell_out(args) 318 | debug_message(report) 319 | lines = re.finditer(".*(?P\d+)\) (?P.*)", report) 320 | 321 | for line in lines: 322 | error = CheckstyleError(line.group("line"), line.group("file")) 323 | self.error_list.append(error) 324 | 325 | 326 | class CodeBeautifier(ShellCommand): 327 | """Concrete class for phpcbf""" 328 | 329 | def execute(self, path): 330 | args = [] 331 | 332 | if pref.get( 333 | "phpcs_php_prefix_path" 334 | ) != "" and self.__class__.__name__ in pref.get("phpcs_commands_to_php_prefix"): 335 | args = [pref.get("phpcs_php_prefix_path")] 336 | 337 | if pref.get("phpcbf_executable_path") != "": 338 | if len(args) > 0: 339 | args.append(pref.get("phpcbf_executable_path")) 340 | else: 341 | args = [pref.get("phpcbf_executable_path")] 342 | else: 343 | debug_message("phpcbf_executable_path is not set, therefore cannot execute") 344 | sublime.error_message( 345 | 'The "phpcbf_executable_path" is not set, therefore cannot execute this command' 346 | ) 347 | return 348 | 349 | args.append(os.path.normpath(path)) 350 | 351 | # Add the additional arguments from the settings file to the command 352 | for key, value in pref.get("phpcbf_additional_args").items(): 353 | arg = key 354 | if value != "": 355 | arg += "=" + value 356 | args.append(arg) 357 | 358 | self.parse_report(args) 359 | 360 | def parse_report(self, args): 361 | report = self.shell_out(args) 362 | debug_message(report) 363 | lines = re.finditer(".*\((?P\d+) fixable violations\)", report) 364 | 365 | for line in lines: 366 | error = CheckstyleError(0, line.group("number") + " fixed violations") 367 | self.error_list.append(error) 368 | 369 | 370 | class MessDetector(ShellCommand): 371 | """Concrete class for PHP Mess Detector""" 372 | 373 | def execute(self, path): 374 | if pref.get("phpmd_run") != True: 375 | return 376 | 377 | args = [] 378 | 379 | if pref.get( 380 | "phpcs_php_prefix_path" 381 | ) != "" and self.__class__.__name__ in pref.get("phpcs_commands_to_php_prefix"): 382 | args = [pref.get("phpcs_php_prefix_path")] 383 | 384 | if pref.get("phpmd_executable_path") != "": 385 | application_path = pref.get("phpmd_executable_path") 386 | else: 387 | application_path = "phpmd" 388 | 389 | if len(args) > 0: 390 | args.append(application_path) 391 | else: 392 | args = [application_path] 393 | 394 | args.append(os.path.normpath(path)) 395 | args.append("text") 396 | 397 | for key, value in pref.get("phpmd_additional_args").items(): 398 | arg = key 399 | if value != "": 400 | arg += "=" + value 401 | args.append(arg) 402 | 403 | self.parse_report(args) 404 | 405 | def parse_report(self, args): 406 | report = self.shell_out(args) 407 | debug_message(report) 408 | lines = re.finditer(".*:(?P\d+)[ \t]+(?P.*)", report) 409 | 410 | for line in lines: 411 | error = CheckstyleError(line.group("line"), line.group("message")) 412 | self.error_list.append(error) 413 | 414 | 415 | class Linter(ShellCommand): 416 | """Content class for php -l""" 417 | 418 | def execute(self, path): 419 | if pref.get("phpcs_linter_run") != True: 420 | return 421 | 422 | if pref.get("phpcs_php_path") != "": 423 | args = [pref.get("phpcs_php_path")] 424 | else: 425 | args = ["php"] 426 | 427 | args.append("-l") 428 | args.append("-d display_errors=On") 429 | args.append(os.path.normpath(path)) 430 | 431 | self.parse_report(args) 432 | 433 | def parse_report(self, args): 434 | report = self.shell_out(args) 435 | debug_message(report) 436 | line = re.search(pref.get("phpcs_linter_regex"), report) 437 | if line != None: 438 | error = CheckstyleError(line.group("line"), line.group("message")) 439 | self.error_list.append(error) 440 | 441 | 442 | class PhpcsCommand: 443 | """Main plugin class for building the checkstyle report""" 444 | 445 | # Class variable, stores the instances. 446 | instances = {} 447 | 448 | @staticmethod 449 | def instance(view, allow_new=True): 450 | """Return the last-used instance for a given view.""" 451 | view_id = view.id() 452 | if view_id not in PhpcsCommand.instances: 453 | if not allow_new: 454 | return False 455 | PhpcsCommand.instances[view_id] = PhpcsCommand(view) 456 | return PhpcsCommand.instances[view_id] 457 | 458 | def __init__(self, view): 459 | self.view = view 460 | self.checkstyle_reports = [] 461 | self.report = [] 462 | self.event = None 463 | self.error_lines = {} 464 | self.error_list = [] 465 | self.shell_commands = ["Linter", "Sniffer", "MessDetector"] 466 | self.standards = [] 467 | 468 | def run(self, path, event=None): 469 | self.event = event 470 | self.checkstyle_reports = [] 471 | self.report = [] 472 | 473 | if event != "on_save": 474 | if pref.get("phpcs_linter_run"): 475 | self.checkstyle_reports.append( 476 | ["Linter", Linter().get_errors(path), "dot"] 477 | ) 478 | if pref.get("phpcs_sniffer_run"): 479 | self.checkstyle_reports.append( 480 | ["Sniffer", Sniffer().get_errors(path), "dot"] 481 | ) 482 | if pref.get("phpmd_run"): 483 | self.checkstyle_reports.append( 484 | ["MessDetector", MessDetector().get_errors(path), "dot"] 485 | ) 486 | else: 487 | if pref.get("phpcs_linter_command_on_save") and pref.get( 488 | "phpcs_linter_run" 489 | ): 490 | self.checkstyle_reports.append( 491 | ["Linter", Linter().get_errors(path), "dot"] 492 | ) 493 | if pref.get("phpcs_command_on_save") and pref.get("phpcs_sniffer_run"): 494 | self.checkstyle_reports.append( 495 | ["Sniffer", Sniffer().get_errors(path), "dot"] 496 | ) 497 | if pref.get("phpmd_command_on_save") and pref.get("phpmd_run"): 498 | self.checkstyle_reports.append( 499 | ["MessDetector", MessDetector().get_errors(path), "dot"] 500 | ) 501 | 502 | sublime.set_timeout(self.generate, 0) 503 | 504 | def clear_sniffer_marks(self): 505 | for region in self.shell_commands: 506 | self.view.erase_regions(region) 507 | 508 | def set_status_bar(self): 509 | if not pref.get("phpcs_show_errors_in_status"): 510 | return 511 | 512 | if self.view.is_scratch(): 513 | return 514 | 515 | line = self.view.rowcol(self.view.sel()[0].end())[0] 516 | errors = self.get_errors(line) 517 | if errors: 518 | self.view.set_status("Phpcs", errors) 519 | else: 520 | self.view.erase_status("Phpcs") 521 | 522 | def generate(self): 523 | self.error_list = [] 524 | region_set = [] 525 | self.error_lines = {} 526 | 527 | for shell_command, report, icon in self.checkstyle_reports: 528 | self.view.erase_regions("checkstyle") 529 | self.view.erase_regions(shell_command) 530 | 531 | debug_message(shell_command + " found " + str(len(report)) + " errors") 532 | for error in report: 533 | line = int(error.get_line()) 534 | pt = self.view.text_point(line - 1, 0) 535 | region_line = self.view.line(pt) 536 | region_set.append(region_line) 537 | self.error_list.append("(" + str(line) + ") " + error.get_message()) 538 | error.set_point(pt) 539 | self.report.append(error) 540 | self.error_lines[line] = error.get_message() 541 | 542 | if len(self.error_list) > 0: 543 | icon = icon if pref.phpcs_show_gutter_marks else "" 544 | outline = ( 545 | sublime.DRAW_OUTLINED 546 | if pref.get("phpcs_outline_for_errors") 547 | else sublime.HIDDEN 548 | ) 549 | if pref.get("phpcs_show_gutter_marks") or pref.get( 550 | "phpcs_outline_for_errors" 551 | ): 552 | if pref.get("phpcs_icon_scope_color") == None: 553 | debug_message( 554 | "WARN: phpcs_icon_scope_color is not defined, so resorting to phpcs colour scope" 555 | ) 556 | pref.set("phpcs_icon_scope_color", "phpcs") 557 | self.view.add_regions( 558 | shell_command, 559 | region_set, 560 | pref.get("phpcs_icon_scope_color"), 561 | icon, 562 | outline, 563 | ) 564 | 565 | if pref.get("phpcs_show_quick_panel") == True: 566 | # Skip showing the errors if we ran on save, and the option isn't set. 567 | if self.event == "on_save" and not pref.get("phpcs_show_errors_on_save"): 568 | return 569 | self.show_quick_panel() 570 | 571 | def show_quick_panel(self): 572 | self.view.window().show_quick_panel(self.error_list, self.on_quick_panel_done) 573 | 574 | def fix_standards_errors(self, tool, path): 575 | self.error_lines = {} 576 | self.error_list = [] 577 | self.report = [] 578 | 579 | if tool == "CodeBeautifier": 580 | fixes = CodeBeautifier().get_errors(path) 581 | else: 582 | fixes = Fixer().get_errors(path) 583 | 584 | for fix in fixes: 585 | self.error_list.append(fix.get_message()) 586 | 587 | if pref.get("php_cs_fixer_show_quick_panel") == True: 588 | self.show_quick_panel() 589 | 590 | def display_coding_standards(self): 591 | self.standards = Sniffer().get_standards_available() 592 | self.view.window().show_quick_panel( 593 | self.standards, self.on_coding_standard_change 594 | ) 595 | 596 | def on_coding_standard_change(self, picked): 597 | if picked == -1: 598 | return 599 | 600 | current_additional_args = pref.get("phpcs_additional_args") 601 | current_additional_args["--standard"] = self.standards[picked].replace(" ", "") 602 | 603 | pref.set("phpcs_additional_args", current_additional_args) 604 | debug_message(current_additional_args) 605 | 606 | def on_quick_panel_done(self, picked): 607 | if picked == -1: 608 | return 609 | 610 | if len(self.report) > 0: 611 | pt = self.report[picked].get_point() 612 | self.view.sel().clear() 613 | self.view.sel().add(sublime.Region(pt)) 614 | self.view.show(pt) 615 | self.set_status_bar() 616 | 617 | def get_errors(self, line): 618 | if not line + 1 in self.error_lines: 619 | return False 620 | 621 | return self.error_lines[line + 1] 622 | 623 | def get_next_error(self, line): 624 | current_line = line + 1 625 | 626 | cache_error = None 627 | # todo: Need a way of getting the line count of the current file! 628 | cache_line = 1000000 629 | for error in self.report: 630 | error_line = error.get_line() 631 | 632 | if cache_error != None: 633 | cache_line = cache_error.get_line() 634 | 635 | if int(error_line) > int(current_line) and int(error_line) < int( 636 | cache_line 637 | ): 638 | cache_error = error 639 | 640 | if cache_error != None: 641 | pt = cache_error.get_point() 642 | self.view.sel().clear() 643 | self.view.sel().add(sublime.Region(pt)) 644 | self.view.show(pt) 645 | 646 | 647 | class PhpcsTextBase(sublime_plugin.TextCommand): 648 | """Base class for Text commands in the plugin, mainly here to check php files""" 649 | 650 | description = "" 651 | 652 | def run(self, args): 653 | debug_message("Not implemented") 654 | 655 | def description(self): 656 | if not PhpcsTextBase.should_execute(self.view): 657 | return "Invalid file format" 658 | else: 659 | return self.description 660 | 661 | @staticmethod 662 | def should_execute(view): 663 | if view.file_name() != None: 664 | try: 665 | ext = os.path.splitext(view.file_name())[1] 666 | result = ext[1:] in pref.get("extensions_to_execute") 667 | except: 668 | debug_message("Is 'extensions_to_execute' setup correctly") 669 | return False 670 | 671 | for block in pref.get("extensions_to_blacklist"): 672 | match = re.search(block, view.file_name()) 673 | if match != None: 674 | return False 675 | 676 | return result 677 | 678 | return False 679 | 680 | 681 | class PhpcsSniffThisFile(PhpcsTextBase): 682 | """Command to sniff the open file""" 683 | 684 | description = "Sniff this file..." 685 | 686 | def run(self, args): 687 | cmd = PhpcsCommand.instance(self.view) 688 | cmd.run(self.view.file_name()) 689 | 690 | def is_enabled(self): 691 | return PhpcsTextBase.should_execute(self.view) 692 | 693 | 694 | class PhpcsShowPreviousErrors(PhpcsTextBase): 695 | """Command to show the previous sniff errors.""" 696 | 697 | description = "Display sniff errors..." 698 | 699 | def run(self, args): 700 | cmd = PhpcsCommand.instance(self.view, False) 701 | cmd.show_quick_panel() 702 | 703 | def is_enabled(self): 704 | """This command is only enabled if it's a PHP buffer with previous errors.""" 705 | return ( 706 | PhpcsTextBase.should_execute(self.view) 707 | and PhpcsCommand.instance(self.view, False) 708 | and len(PhpcsCommand.instance(self.view, False).error_list) > 0 709 | ) 710 | 711 | 712 | class PhpcsGotoNextErrorCommand(PhpcsTextBase): 713 | """Go to the next error from the current position""" 714 | 715 | def run(self, args): 716 | line = self.view.rowcol(self.view.sel()[0].end())[0] 717 | 718 | cmd = PhpcsCommand.instance(self.view) 719 | next_line = cmd.get_next_error(line) 720 | 721 | def is_enabled(self): 722 | """This command is only enabled if it's a PHP buffer with previous errors.""" 723 | 724 | return ( 725 | PhpcsTextBase.should_execute(self.view) 726 | and PhpcsCommand.instance(self.view, False) 727 | and len(PhpcsCommand.instance(self.view, False).error_list) > 0 728 | ) 729 | 730 | 731 | class PhpcsClearSnifferMarksCommand(PhpcsTextBase): 732 | """Command to clear the sniffer marks from the view""" 733 | 734 | description = "Clear sniffer marks..." 735 | 736 | def run(self, args): 737 | cmd = PhpcsCommand.instance(self.view) 738 | cmd.clear_sniffer_marks() 739 | 740 | def is_enabled(self): 741 | return PhpcsTextBase.should_execute(self.view) 742 | 743 | 744 | class PhpcsFixThisFileCommand(PhpcsTextBase): 745 | """Command to use php-cs-fixer to 'fix' the file""" 746 | 747 | description = "Fix coding standard issues (php-cs-fixer)" 748 | 749 | def run(self, args, tool="Fixer"): 750 | debug_message(tool) 751 | cmd = PhpcsCommand.instance(self.view) 752 | cmd.fix_standards_errors(tool, self.view.file_name()) 753 | 754 | def is_enabled(self): 755 | return PhpcsTextBase.should_execute(self.view) 756 | 757 | 758 | class PhpcsFixThisDirectoryCommand(sublime_plugin.WindowCommand): 759 | """Command to use php-cs-fixer to 'fix' the directory""" 760 | 761 | def run(self, tool="Fixer", paths=[]): 762 | cmd = PhpcsCommand.instance(self.window.active_view()) 763 | cmd.fix_standards_errors(tool, os.path.normpath(paths[0])) 764 | 765 | def is_enabled(self): 766 | if pref.get("php_cs_fixer_executable_path") != "": 767 | return True 768 | else: 769 | return False 770 | 771 | def is_visible(self, paths=[]): 772 | return True 773 | 774 | def description(self, paths=[]): 775 | return "Fix this directory (PHP-CS-Fixer)" 776 | 777 | 778 | class PhpcsTogglePlugin(PhpcsTextBase): 779 | """Command to toggle if plugin should execute on save""" 780 | 781 | def run(self, edit, toggle=None): 782 | if toggle == None: 783 | if pref.get("phpcs_execute_on_save") == True: 784 | pref.set("phpcs_execute_on_save", False) 785 | else: 786 | pref.set("phpcs_execute_on_save", True) 787 | else: 788 | if toggle: 789 | pref.set("phpcs_execute_on_save", True) 790 | else: 791 | pref.set("phpcs_execute_on_save", False) 792 | 793 | def is_enabled(self): 794 | return PhpcsTextBase.should_execute(self.view) 795 | 796 | def description(self, paths=[]): 797 | if pref.get("phpcs_execute_on_save") == True: 798 | description = "Turn Execute On Save Off" 799 | else: 800 | description = "Turn Execute On Save On" 801 | return description 802 | 803 | 804 | class PhpcsSwitchCodingStandard(PhpcsTextBase): 805 | """Ability to switch the coding standard for this session""" 806 | 807 | def run(self, args): 808 | cmd = PhpcsCommand.instance(self.view) 809 | cmd.display_coding_standards() 810 | 811 | def is_enabled(self): 812 | return PhpcsTextBase.should_execute(self.view) 813 | 814 | 815 | class PhpcsEventListener(sublime_plugin.EventListener): 816 | """Event listener for the plugin""" 817 | 818 | def on_post_save(self, view): 819 | if PhpcsTextBase.should_execute(view): 820 | if pref.get("phpcs_execute_on_save") == True: 821 | cmd = PhpcsCommand.instance(view) 822 | thread = threading.Thread( 823 | target=cmd.run, args=(view.file_name(), "on_save") 824 | ) 825 | thread.start() 826 | 827 | if ( 828 | pref.get("phpcs_execute_on_save") == True 829 | and pref.get("php_cs_fixer_on_save") == True 830 | ): 831 | cmd = PhpcsCommand.instance(view) 832 | cmd.fix_standards_errors("Fixer", view.file_name()) 833 | 834 | if ( 835 | pref.get("phpcs_execute_on_save") == True 836 | and pref.get("phpcbf_on_save") == True 837 | ): 838 | cmd = PhpcsCommand.instance(view) 839 | cmd.fix_standards_errors("CodeBeautifier", view.file_name()) 840 | 841 | def on_selection_modified(self, view): 842 | if not PhpcsTextBase.should_execute(view): 843 | return 844 | 845 | cmd = PhpcsCommand.instance(view, False) 846 | if isinstance(cmd, PhpcsCommand): 847 | cmd.set_status_bar() 848 | 849 | def on_pre_save(self, view): 850 | """Project based settings, currently able to see an API based way of doing this!""" 851 | if not PhpcsTextBase.should_execute(view) or st_version == 2: 852 | return 853 | 854 | current_project_file = view.window().project_file_name() 855 | debug_message("Project files:") 856 | debug_message(" Current: " + str(current_project_file)) 857 | debug_message(" Last Known: " + str(pref.get("project_file"))) 858 | 859 | if current_project_file == None: 860 | debug_message("No project file defined, therefore skipping reload") 861 | return 862 | 863 | if pref.get("project_file") == current_project_file: 864 | debug_message("Project files are the same, skipping reload") 865 | else: 866 | debug_message("Project files have changed, commence the reload") 867 | pref.load() 868 | pref.set("project_file", current_project_file) 869 | -------------------------------------------------------------------------------- /phpcs.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // Plugin settings 3 | 4 | // Turn the debug output on/off 5 | "show_debug": false, 6 | 7 | // Which file types (file extensions), do you want the plugin to 8 | // execute for 9 | "extensions_to_execute": ["php"], 10 | 11 | // Do we need to blacklist any sub extensions from extensions_to_execute 12 | // An example would be ["twig.php"] 13 | "extensions_to_blacklist": [], 14 | 15 | // Execute the sniffer on file save 16 | "phpcs_execute_on_save": true, 17 | 18 | // Show the error list after save. 19 | "phpcs_show_errors_on_save": true, 20 | 21 | // Show the errors in the gutter 22 | "phpcs_show_gutter_marks": true, 23 | 24 | // Show outline for errors 25 | "phpcs_outline_for_errors": true, 26 | 27 | // Show the errors in the status bar 28 | "phpcs_show_errors_in_status": true, 29 | 30 | // Show the errors in the quick panel so you can then goto line 31 | "phpcs_show_quick_panel": true, 32 | 33 | // The path to the php executable. 34 | // Needed for windows, or anyone who doesn't/can't make phars 35 | // executable. Avoid setting this if at all possible 36 | "phpcs_php_prefix_path": "", 37 | 38 | // Options include: 39 | // - Sniffer 40 | // - Fixer 41 | // - MessDetector 42 | // - CodeBeautifier 43 | // 44 | // This will prepend the application with the path to php 45 | // Needed for windows, or anyone who doesn't/can't make phars 46 | // executable. Avoid setting this if at all possible 47 | "phpcs_commands_to_php_prefix": [], 48 | 49 | // What color to stylise the icon 50 | // https://www.sublimetext.com/docs/3/api_reference.html#sublime.View 51 | // add_regions 52 | "phpcs_icon_scope_color": "comment", 53 | 54 | // PHP_CodeSniffer settings 55 | 56 | // Do you want to run the phpcs checker? 57 | "phpcs_sniffer_run": true, 58 | 59 | // Execute the sniffer on file save 60 | "phpcs_command_on_save": true, 61 | 62 | // It seems python/sublime cannot always find the phpcs application 63 | // If empty, then use PATH version of phpcs, else use the set value 64 | "phpcs_executable_path": "", 65 | 66 | // Additional arguments you can specify into the application 67 | // 68 | // Example: 69 | // { 70 | // "--standard": "PEAR", 71 | // "-n" 72 | // } 73 | "phpcs_additional_args": { 74 | "--standard": "PSR2", 75 | "-n": "" 76 | }, 77 | 78 | // PHP-CS-Fixer settings 79 | 80 | // Fix the issues on save 81 | "php_cs_fixer_on_save": false, 82 | 83 | // Show the quick panel 84 | "php_cs_fixer_show_quick_panel": false, 85 | 86 | // Path to where you have the php-cs-fixer installed 87 | "php_cs_fixer_executable_path": "", 88 | 89 | // Additional arguments you can specify into the application 90 | "php_cs_fixer_additional_args": {}, 91 | 92 | // phpcbf settings 93 | 94 | // Fix the issues on save 95 | "phpcbf_on_save": false, 96 | 97 | // Show the quick panel 98 | "phpcbf_show_quick_panel": false, 99 | 100 | // Path to where you have the phpcbf installed 101 | "phpcbf_executable_path": "", 102 | 103 | // Additional arguments you can specify into the application 104 | // 105 | // Example: 106 | // { 107 | // "--level": "all" 108 | // } 109 | "phpcbf_additional_args": { 110 | "--standard": "PSR2", 111 | "-n": "" 112 | }, 113 | 114 | // PHP Linter settings 115 | 116 | // Are we going to run php -l over the file? 117 | "phpcs_linter_run": true, 118 | 119 | // Execute the linter on file save 120 | "phpcs_linter_command_on_save": true, 121 | 122 | // It seems python/sublime cannot always find the php application 123 | // If empty, then use PATH version of php, else use the set value 124 | "phpcs_php_path": "", 125 | 126 | // What is the regex for the linter? Has to provide a named match for 'message' and 'line' 127 | "phpcs_linter_regex": "(?P.*) on line (?P\\d+)", 128 | 129 | // PHP Mess Detector settings 130 | 131 | // Execute phpmd 132 | "phpmd_run": false, 133 | 134 | // Execute the phpmd on file save 135 | "phpmd_command_on_save": true, 136 | 137 | // It seems python/sublime cannot always find the phpmd application 138 | // If empty, then use PATH version of phpmd, else use the set value 139 | "phpmd_executable_path": "", 140 | 141 | // Additional arguments you can specify into the application 142 | // 143 | // Example: 144 | // { 145 | // "codesize,unusedcode" 146 | // } 147 | "phpmd_additional_args": { 148 | "codesize,unusedcode,naming": "" 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /tests/test_phpcs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | from unittest import TestCase 5 | from unittest.mock import patch 6 | 7 | import sublime 8 | 9 | from Phpcs.phpcs import Sniffer 10 | 11 | 12 | class TestSniffer(TestCase): 13 | def test_we_can_build_up_the_correct_executable_string_when_we_prefix(self): 14 | php_path = "/opt/homebrew/bin/php" 15 | s = sublime.load_settings("phpcs.sublime-settings") 16 | 17 | s.set("phpcs_php_prefix_path", php_path) 18 | s.set("phpcs_commands_to_php_prefix", "Sniffer") 19 | 20 | args = Sniffer().get_executable_args() 21 | self.assertIn(php_path, args) 22 | 23 | def test_we_can_build_up_the_correct_executable_string_when_we_dont_prefix(self): 24 | s = sublime.load_settings("phpcs.sublime-settings") 25 | 26 | s.set("phpcs_php_prefix_path", "/opt/homebrew/bin/php") 27 | s.set("phpcs_commands_to_php_prefix", "") 28 | s.set("phpcs_executable_path", "") 29 | 30 | args = Sniffer().get_executable_args() 31 | self.assertIn("phpcs", args) 32 | 33 | @patch("Phpcs.phpcs.Sniffer.shell_out") 34 | def test_we_can_parse_phpcs_standards_output(self, shell_mock): 35 | shell_mock.return_value = ( 36 | "The installed coding standards are One, NeutronStandard, Two and Three" 37 | ) 38 | standards = Sniffer().get_standards_available() 39 | 40 | expected = ["One", "NeutronStandard", "Two", "Three"] 41 | 42 | self.assertEqual(expected, standards) 43 | --------------------------------------------------------------------------------