├── LICENSE.txt ├── assets ├── icon.png └── phpcs-integration │ ├── Extension │ ├── File.php │ └── Fixer.php │ ├── Handlers │ ├── CodeAction.php │ ├── Diagnostic.php │ ├── Format.php │ └── Handler.php │ ├── README.md │ └── VSCodeIntegration.php └── composer.json /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This program is free software; you can redistribute it and/or modify 2 | it under the terms of the GNU General Public License as published by 3 | the Free Software Foundation; either version 2 of the License, or 4 | (at your option) any later version. 5 | 6 | This program is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | GNU General Public License for more details. 10 | 11 | You should have received a copy of the GNU General Public License 12 | along with this program; if not, write to the Free Software 13 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 14 | 15 | 16 | =================================== 17 | 18 | 19 | GNU GENERAL PUBLIC LICENSE 20 | Version 2, June 1991 21 | 22 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 23 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 24 | Everyone is permitted to copy and distribute verbatim copies 25 | of this license document, but changing it is not allowed. 26 | 27 | Preamble 28 | 29 | The licenses for most software are designed to take away your 30 | freedom to share and change it. By contrast, the GNU General Public 31 | License is intended to guarantee your freedom to share and change free 32 | software--to make sure the software is free for all its users. This 33 | General Public License applies to most of the Free Software 34 | Foundation's software and to any other program whose authors commit to 35 | using it. (Some other Free Software Foundation software is covered by 36 | the GNU Lesser General Public License instead.) You can apply it to 37 | your programs, too. 38 | 39 | When we speak of free software, we are referring to freedom, not 40 | price. Our General Public Licenses are designed to make sure that you 41 | have the freedom to distribute copies of free software (and charge for 42 | this service if you wish), that you receive source code or can get it 43 | if you want it, that you can change the software or use pieces of it 44 | in new free programs; and that you know you can do these things. 45 | 46 | To protect your rights, we need to make restrictions that forbid 47 | anyone to deny you these rights or to ask you to surrender the rights. 48 | These restrictions translate to certain responsibilities for you if you 49 | distribute copies of the software, or if you modify it. 50 | 51 | For example, if you distribute copies of such a program, whether 52 | gratis or for a fee, you must give the recipients all the rights that 53 | you have. You must make sure that they, too, receive or can get the 54 | source code. And you must show them these terms so they know their 55 | rights. 56 | 57 | We protect your rights with two steps: (1) copyright the software, and 58 | (2) offer you this license which gives you legal permission to copy, 59 | distribute and/or modify the software. 60 | 61 | Also, for each author's protection and ours, we want to make certain 62 | that everyone understands that there is no warranty for this free 63 | software. If the software is modified by someone else and passed on, we 64 | want its recipients to know that what they have is not the original, so 65 | that any problems introduced by others will not reflect on the original 66 | authors' reputations. 67 | 68 | Finally, any free program is threatened constantly by software 69 | patents. We wish to avoid the danger that redistributors of a free 70 | program will individually obtain patent licenses, in effect making the 71 | program proprietary. To prevent this, we have made it clear that any 72 | patent must be licensed for everyone's free use or not licensed at all. 73 | 74 | The precise terms and conditions for copying, distribution and 75 | modification follow. 76 | 77 | GNU GENERAL PUBLIC LICENSE 78 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 79 | 80 | 0. This License applies to any program or other work which contains 81 | a notice placed by the copyright holder saying it may be distributed 82 | under the terms of this General Public License. The "Program", below, 83 | refers to any such program or work, and a "work based on the Program" 84 | means either the Program or any derivative work under copyright law: 85 | that is to say, a work containing the Program or a portion of it, 86 | either verbatim or with modifications and/or translated into another 87 | language. (Hereinafter, translation is included without limitation in 88 | the term "modification".) Each licensee is addressed as "you". 89 | 90 | Activities other than copying, distribution and modification are not 91 | covered by this License; they are outside its scope. The act of 92 | running the Program is not restricted, and the output from the Program 93 | is covered only if its contents constitute a work based on the 94 | Program (independent of having been made by running the Program). 95 | Whether that is true depends on what the Program does. 96 | 97 | 1. You may copy and distribute verbatim copies of the Program's 98 | source code as you receive it, in any medium, provided that you 99 | conspicuously and appropriately publish on each copy an appropriate 100 | copyright notice and disclaimer of warranty; keep intact all the 101 | notices that refer to this License and to the absence of any warranty; 102 | and give any other recipients of the Program a copy of this License 103 | along with the Program. 104 | 105 | You may charge a fee for the physical act of transferring a copy, and 106 | you may at your option offer warranty protection in exchange for a fee. 107 | 108 | 2. You may modify your copy or copies of the Program or any portion 109 | of it, thus forming a work based on the Program, and copy and 110 | distribute such modifications or work under the terms of Section 1 111 | above, provided that you also meet all of these conditions: 112 | 113 | a) You must cause the modified files to carry prominent notices 114 | stating that you changed the files and the date of any change. 115 | 116 | b) You must cause any work that you distribute or publish, that in 117 | whole or in part contains or is derived from the Program or any 118 | part thereof, to be licensed as a whole at no charge to all third 119 | parties under the terms of this License. 120 | 121 | c) If the modified program normally reads commands interactively 122 | when run, you must cause it, when started running for such 123 | interactive use in the most ordinary way, to print or display an 124 | announcement including an appropriate copyright notice and a 125 | notice that there is no warranty (or else, saying that you provide 126 | a warranty) and that users may redistribute the program under 127 | these conditions, and telling the user how to view a copy of this 128 | License. (Exception: if the Program itself is interactive but 129 | does not normally print such an announcement, your work based on 130 | the Program is not required to print an announcement.) 131 | 132 | These requirements apply to the modified work as a whole. If 133 | identifiable sections of that work are not derived from the Program, 134 | and can be reasonably considered independent and separate works in 135 | themselves, then this License, and its terms, do not apply to those 136 | sections when you distribute them as separate works. But when you 137 | distribute the same sections as part of a whole which is a work based 138 | on the Program, the distribution of the whole must be on the terms of 139 | this License, whose permissions for other licensees extend to the 140 | entire whole, and thus to each and every part regardless of who wrote it. 141 | 142 | Thus, it is not the intent of this section to claim rights or contest 143 | your rights to work written entirely by you; rather, the intent is to 144 | exercise the right to control the distribution of derivative or 145 | collective works based on the Program. 146 | 147 | In addition, mere aggregation of another work not based on the Program 148 | with the Program (or with a work based on the Program) on a volume of 149 | a storage or distribution medium does not bring the other work under 150 | the scope of this License. 151 | 152 | 3. You may copy and distribute the Program (or a work based on it, 153 | under Section 2) in object code or executable form under the terms of 154 | Sections 1 and 2 above provided that you also do one of the following: 155 | 156 | a) Accompany it with the complete corresponding machine-readable 157 | source code, which must be distributed under the terms of Sections 158 | 1 and 2 above on a medium customarily used for software interchange; or, 159 | 160 | b) Accompany it with a written offer, valid for at least three 161 | years, to give any third party, for a charge no more than your 162 | cost of physically performing source distribution, a complete 163 | machine-readable copy of the corresponding source code, to be 164 | distributed under the terms of Sections 1 and 2 above on a medium 165 | customarily used for software interchange; or, 166 | 167 | c) Accompany it with the information you received as to the offer 168 | to distribute corresponding source code. (This alternative is 169 | allowed only for noncommercial distribution and only if you 170 | received the program in object code or executable form with such 171 | an offer, in accord with Subsection b above.) 172 | 173 | The source code for a work means the preferred form of the work for 174 | making modifications to it. For an executable work, complete source 175 | code means all the source code for all modules it contains, plus any 176 | associated interface definition files, plus the scripts used to 177 | control compilation and installation of the executable. However, as a 178 | special exception, the source code distributed need not include 179 | anything that is normally distributed (in either source or binary 180 | form) with the major components (compiler, kernel, and so on) of the 181 | operating system on which the executable runs, unless that component 182 | itself accompanies the executable. 183 | 184 | If distribution of executable or object code is made by offering 185 | access to copy from a designated place, then offering equivalent 186 | access to copy the source code from the same place counts as 187 | distribution of the source code, even though third parties are not 188 | compelled to copy the source along with the object code. 189 | 190 | 4. You may not copy, modify, sublicense, or distribute the Program 191 | except as expressly provided under this License. Any attempt 192 | otherwise to copy, modify, sublicense or distribute the Program is 193 | void, and will automatically terminate your rights under this License. 194 | However, parties who have received copies, or rights, from you under 195 | this License will not have their licenses terminated so long as such 196 | parties remain in full compliance. 197 | 198 | 5. You are not required to accept this License, since you have not 199 | signed it. However, nothing else grants you permission to modify or 200 | distribute the Program or its derivative works. These actions are 201 | prohibited by law if you do not accept this License. Therefore, by 202 | modifying or distributing the Program (or any work based on the 203 | Program), you indicate your acceptance of this License to do so, and 204 | all its terms and conditions for copying, distributing or modifying 205 | the Program or works based on it. 206 | 207 | 6. Each time you redistribute the Program (or any work based on the 208 | Program), the recipient automatically receives a license from the 209 | original licensor to copy, distribute or modify the Program subject to 210 | these terms and conditions. You may not impose any further 211 | restrictions on the recipients' exercise of the rights granted herein. 212 | You are not responsible for enforcing compliance by third parties to 213 | this License. 214 | 215 | 7. If, as a consequence of a court judgment or allegation of patent 216 | infringement or for any other reason (not limited to patent issues), 217 | conditions are imposed on you (whether by court order, agreement or 218 | otherwise) that contradict the conditions of this License, they do not 219 | excuse you from the conditions of this License. If you cannot 220 | distribute so as to satisfy simultaneously your obligations under this 221 | License and any other pertinent obligations, then as a consequence you 222 | may not distribute the Program at all. For example, if a patent 223 | license would not permit royalty-free redistribution of the Program by 224 | all those who receive copies directly or indirectly through you, then 225 | the only way you could satisfy both it and this License would be to 226 | refrain entirely from distribution of the Program. 227 | 228 | If any portion of this section is held invalid or unenforceable under 229 | any particular circumstance, the balance of the section is intended to 230 | apply and the section as a whole is intended to apply in other 231 | circumstances. 232 | 233 | It is not the purpose of this section to induce you to infringe any 234 | patents or other property right claims or to contest validity of any 235 | such claims; this section has the sole purpose of protecting the 236 | integrity of the free software distribution system, which is 237 | implemented by public license practices. Many people have made 238 | generous contributions to the wide range of software distributed 239 | through that system in reliance on consistent application of that 240 | system; it is up to the author/donor to decide if he or she is willing 241 | to distribute software through any other system and a licensee cannot 242 | impose that choice. 243 | 244 | This section is intended to make thoroughly clear what is believed to 245 | be a consequence of the rest of this License. 246 | 247 | 8. If the distribution and/or use of the Program is restricted in 248 | certain countries either by patents or by copyrighted interfaces, the 249 | original copyright holder who places the Program under this License 250 | may add an explicit geographical distribution limitation excluding 251 | those countries, so that distribution is permitted only in or among 252 | countries not thus excluded. In such case, this License incorporates 253 | the limitation as if written in the body of this License. 254 | 255 | 9. The Free Software Foundation may publish revised and/or new versions 256 | of the General Public License from time to time. Such new versions will 257 | be similar in spirit to the present version, but may differ in detail to 258 | address new problems or concerns. 259 | 260 | Each version is given a distinguishing version number. If the Program 261 | specifies a version number of this License which applies to it and "any 262 | later version", you have the option of following the terms and conditions 263 | either of that version or of any later version published by the Free 264 | Software Foundation. If the Program does not specify a version number of 265 | this License, you may choose any version ever published by the Free Software 266 | Foundation. 267 | 268 | 10. If you wish to incorporate parts of the Program into other free 269 | programs whose distribution conditions are different, write to the author 270 | to ask for permission. For software which is copyrighted by the Free 271 | Software Foundation, write to the Free Software Foundation; we sometimes 272 | make exceptions for this. Our decision will be guided by the two goals 273 | of preserving the free status of all derivatives of our free software and 274 | of promoting the sharing and reuse of software generally. 275 | 276 | NO WARRANTY 277 | 278 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 279 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 280 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 281 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 282 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 283 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 284 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 285 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 286 | REPAIR OR CORRECTION. 287 | 288 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 289 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 290 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 291 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 292 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 293 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 294 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 295 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 296 | POSSIBILITY OF SUCH DAMAGES. 297 | 298 | END OF TERMS AND CONDITIONS 299 | 300 | How to Apply These Terms to Your New Programs 301 | 302 | If you develop a new program, and you want it to be of the greatest 303 | possible use to the public, the best way to achieve this is to make it 304 | free software which everyone can redistribute and change under these terms. 305 | 306 | To do so, attach the following notices to the program. It is safest 307 | to attach them to the start of each source file to most effectively 308 | convey the exclusion of warranty; and each file should have at least 309 | the "copyright" line and a pointer to where the full notice is found. 310 | 311 | 312 | Copyright (C) 313 | 314 | This program is free software; you can redistribute it and/or modify 315 | it under the terms of the GNU General Public License as published by 316 | the Free Software Foundation; either version 2 of the License, or 317 | (at your option) any later version. 318 | 319 | This program is distributed in the hope that it will be useful, 320 | but WITHOUT ANY WARRANTY; without even the implied warranty of 321 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 322 | GNU General Public License for more details. 323 | 324 | You should have received a copy of the GNU General Public License along 325 | with this program; if not, write to the Free Software Foundation, Inc., 326 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 327 | 328 | Also add information on how to contact you by electronic and paper mail. 329 | 330 | If the program is interactive, make it output a short notice like this 331 | when it starts in an interactive mode: 332 | 333 | Gnomovision version 69, Copyright (C) year name of author 334 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 335 | This is free software, and you are welcome to redistribute it 336 | under certain conditions; type `show c' for details. 337 | 338 | The hypothetical commands `show w' and `show c' should show the appropriate 339 | parts of the General Public License. Of course, the commands you use may 340 | be called something other than `show w' and `show c'; they could even be 341 | mouse-clicks or menu items--whatever suits your program. 342 | 343 | You should also get your employer (if you work as a programmer) or your 344 | school, if any, to sign a "copyright disclaimer" for the program, if 345 | necessary. Here is a sample; alter the names: 346 | 347 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 348 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 349 | 350 | , 1 April 1989 351 | Ty Coon, President of Vice 352 | 353 | This General Public License does not permit incorporating your program into 354 | proprietary programs. If your program is a subroutine library, you may 355 | consider it more useful to permit linking proprietary applications with the 356 | library. If this is what you want to do, use the GNU Lesser General 357 | Public License instead of this License. 358 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ObliviousHarmony/vscode-php-codesniffer/9ebba9b0c1493335ec42096350956c0c7ef1cae0/assets/icon.png -------------------------------------------------------------------------------- /assets/phpcs-integration/Extension/File.php: -------------------------------------------------------------------------------- 1 | content = $phpcsFile->content; 60 | $this->tokens = $phpcsFile->tokens; 61 | $this->tokenizerType = $phpcsFile->tokenizerType; 62 | $this->tokenizer = $phpcsFile->tokenizer; 63 | $this->eolChar = $phpcsFile->eolChar; 64 | $this->numTokens = $phpcsFile->numTokens; 65 | $this->fixableCount = $phpcsFile->fixableCount; 66 | 67 | parent::__construct($phpcsFile->path, $phpcsFile->ruleset, $phpcsFile->config); 68 | 69 | $this->prepareTokensForVSCode(); 70 | } 71 | 72 | /** 73 | * Parses the file and ensures that the tokens have been prepared for VS Code. 74 | * 75 | * @inheritDoc 76 | */ 77 | public function parse() 78 | { 79 | parent::parse(); 80 | $this->prepareTokensForVSCode(); 81 | } 82 | 83 | /** 84 | * Gets the stack pointer for a position. 85 | * 86 | * @param int $line The line to check. 87 | * @param int $column The column to check. 88 | * @param bool $useRangeFormat Indicates we should find the VS Code range format. 89 | * @return int|null 90 | */ 91 | public function getStackPtrForPosition($line, $column, $useRangeFormat = false) 92 | { 93 | if ($useRangeFormat) { 94 | if (!isset($this->tokenPositionMap[$line . ':' . $column])) { 95 | return null; 96 | } 97 | 98 | return $this->tokenPositionMap[$line . ':' . $column]; 99 | } 100 | 101 | if (!isset($this->tokenPositionMap[$line][$column])) { 102 | return null; 103 | } 104 | 105 | return $this->tokenPositionMap[$line][$column]; 106 | } 107 | 108 | /** 109 | * Gets a specific token. 110 | * 111 | * @param int $stackPtr The token pointer to fetch. 112 | * @return array 113 | */ 114 | public function getToken($stackPtr) 115 | { 116 | return $this->tokens[$stackPtr]; 117 | } 118 | 119 | /** 120 | * Formats a document range and returns the changed content as a result. 121 | * 122 | * @param int|null $startToken The token to start formatting from. 123 | * @param int|null $endToken The token to end formatting on. 124 | * @return string 125 | */ 126 | public function formatRange($startToken, $endToken) 127 | { 128 | $this->formatStartToken = $startToken; 129 | $this->formatEndToken = $endToken; 130 | 131 | // Fix the file. 132 | $this->fixer->enabled = true; 133 | $this->fixer->startFile($this); 134 | $this->fixer->fixFile(); 135 | 136 | // Set us back so that the fixer will operate normally. 137 | $this->formatStartToken = null; 138 | $this->formatEndToken = null; 139 | 140 | // Make sure the caller knows when nothing has been formatted. 141 | if ($this->fixedCount <= 0) { 142 | return false; 143 | } 144 | 145 | return $this->fixer->getContents(); 146 | } 147 | 148 | /** 149 | * Fixes a single code action and returns the content changed as a result. 150 | * 151 | * @param int $sourceStackPtr The position we want to fix. 152 | * @param string $source The problem we want to fix. 153 | * @return array 154 | */ 155 | public function fixCodeAction($sourceStackPtr, $source) 156 | { 157 | $sniff = $this->getSniffFromMessageSource($source); 158 | if (!isset($sniff)) { 159 | return array(); 160 | } 161 | 162 | if ($this->ignored === true) { 163 | return array(); 164 | } 165 | 166 | // Replace the fixer with a custom one that can give us insight into 167 | // the specific tokens that have been replaced. 168 | $fixer = $this->fixer; 169 | $this->fixer = new Fixer(); 170 | $this->fixer->enabled = true; 171 | $this->fixer->startFile($this); 172 | 173 | // Record the token that we're allowing to change so that we don't fix 174 | // any problem other than the one we're asking for. 175 | $this->codeActionToken = $sourceStackPtr; 176 | $this->codeActionSource = substr($source, strrpos($source, '.') + 1); 177 | 178 | // Make sure we're only processing allowed tokens. 179 | $allowedTokens = $sniff->register(); 180 | 181 | // Apply the single sniff and get all of the tokens that were changed by it. 182 | for ($i = 0; $i < $this->numTokens; ++$i) { 183 | $token = $this->tokens[$i]; 184 | if (!in_array($token['code'], $allowedTokens, true)) { 185 | continue; 186 | } 187 | 188 | $skip = $sniff->process($this, $i); 189 | // Make sure to support the skipping that sniffs do. 190 | if (isset($skip)) { 191 | $i = $skip; 192 | } 193 | } 194 | 195 | // We're going to use the tokens that were changed by the sniff to detect specific edits to make to the file. 196 | $changedTokens = $this->fixer->getTextEditTokens(); 197 | 198 | // Set us back so that the fixer will operate normally. 199 | $this->fixer = $fixer; 200 | $this->codeActionToken = null; 201 | $this->codeActionSource = null; 202 | 203 | return $this->getTextEdits($changedTokens); 204 | } 205 | 206 | /** 207 | * Attempts to record a message if we aren't actively ignoring it. 208 | * 209 | * @inheritDoc 210 | */ 211 | protected function addMessage($error, $message, $line, $column, $code, $data, $severity, $fixable) 212 | { 213 | // Check to see if we're only looking for a specific subset of messages. 214 | $stackPtr = $this->getStackPtrForPosition($line, $column); 215 | if (isset($stackPtr)) { 216 | if (isset($this->codeActionToken)) { 217 | // We will assume that the error can be recorded because it wouldn't be in here otherwise. 218 | return $this->codeActionToken === $stackPtr && $this->codeActionSource === $code; 219 | } 220 | 221 | // Check the format range if one is set. 222 | if (isset($this->formatStartToken) && $stackPtr < $this->formatStartToken) { 223 | return false; 224 | } 225 | if (isset($this->formatEndToken) && $stackPtr > $this->formatEndToken) { 226 | return false; 227 | } 228 | } 229 | 230 | return parent::addMessage($error, $message, $line, $column, $code, $data, $severity, $fixable); 231 | } 232 | 233 | /** 234 | * Prepares the tokens for the VS Code file class functionality. 235 | */ 236 | private function prepareTokensForVSCode() 237 | { 238 | $this->tokenPositionMap = array(); 239 | 240 | // Review the tokens to populate some common data for us to work with. 241 | $columnOffset = -1; 242 | $lineWithOffset = -1; 243 | foreach ($this->tokens as $stackPtr => $token) { 244 | $line = $token['line']; 245 | $column = $token['column']; 246 | 247 | // Every new line should restart calculating the offsets. 248 | if ($lineWithOffset !== $line) { 249 | $lineWithOffset = $line; 250 | $columnOffset = 0; 251 | } 252 | 253 | // We should make a distinction between the orig_content and content 254 | // because PHPCS sometimes performs transformations internally 255 | // to make the data easier to work with. 256 | if (isset($token['orig_content'])) { 257 | $columnWidth = mb_strlen($token['orig_content']); 258 | $endsWithNewline = substr($token['orig_content'], -1) === "\n"; 259 | } else { 260 | $columnWidth = $token['length']; 261 | $endsWithNewline = substr($token['content'], -1) === "\n"; 262 | } 263 | 264 | $originalColumn = $column - $columnOffset; 265 | 266 | // Build a range object to represent this token in VS Code. 267 | if ($endsWithNewline) { 268 | // Newlines should wrap the range to the start of the next line. 269 | $range = array( 270 | 'startLine' => $line - 1, 271 | 'startCharacter' => $originalColumn - 1, 272 | 'endLine' => $line, 273 | 'endCharacter' => 0 274 | ); 275 | } else { 276 | $range = array( 277 | 'startLine' => $line - 1, 278 | 'startCharacter' => $originalColumn - 1, 279 | 'endLine' => $line - 1, 280 | 'endCharacter' => $originalColumn + $columnWidth - 1 281 | ); 282 | } 283 | 284 | // Store the range object to use elsewhere. 285 | $this->tokens[$stackPtr]['vscode_range'] = $range; 286 | 287 | // Create a map to convert from a line/character position to a token pointer. 288 | for ($mapPos = $column; $mapPos <= $column + $token['length']; $mapPos++) { 289 | $this->tokenPositionMap[$line][$mapPos] = $stackPtr; 290 | } 291 | 292 | // Do the same with range positions. 293 | for ($mapPos = $range['startCharacter']; $mapPos <= $range['startCharacter'] + $columnWidth; ++$mapPos) { 294 | $this->tokenPositionMap[$range['startLine'] . ':' . $mapPos] = $stackPtr; 295 | } 296 | 297 | // Our offset is the difference between the old and new lengths. 298 | $columnOffset += $token['length'] - $columnWidth; 299 | } 300 | } 301 | 302 | /** 303 | * Fetches the sniff class instance for the given message source. 304 | * 305 | * @param string $source 306 | * @return Sniff|null 307 | */ 308 | private function getSniffFromMessageSource($source) 309 | { 310 | // Transform the source into a sniff code so we can use it to get the sniff instance. 311 | $sniffCode = explode('.', $source, 4); 312 | $sniffCode = $sniffCode[0] . '.' . $sniffCode[1] . '.' . $sniffCode[2]; 313 | if (!isset($this->ruleset->sniffCodes[$sniffCode])) { 314 | return null; 315 | } 316 | $sniffCode = $this->ruleset->sniffCodes[$sniffCode]; 317 | 318 | return $this->ruleset->sniffs[$sniffCode]; 319 | } 320 | 321 | /** 322 | * Takes an array of changed tokens and merged contiguous blocks into text edits. 323 | * 324 | * @param array $changedTokens All of the tokens that changed. 325 | * @return array 326 | */ 327 | private function getTextEdits($changedTokens) 328 | { 329 | $codeActionEdits = array(); 330 | 331 | // Transform the changes into contiguous blocks to make the edits as small as possible. 332 | foreach ($changedTokens as $stackPtr => $newContent) { 333 | $token = $this->tokens[$stackPtr]; 334 | $range = $token['vscode_range']; 335 | 336 | // Begin recording if we aren't already. 337 | if (!isset($replacementContent)) { 338 | $startLine = $range['startLine']; 339 | $startCharacter = $range['startCharacter']; 340 | $endLine = null; 341 | $endCharacter = null; 342 | $replacementContent = ''; 343 | } 344 | 345 | // Keep moving the end until we reach the end of a block. 346 | $endLine = $range['endLine']; 347 | $endCharacter = $range['endCharacter']; 348 | 349 | // Record the content in the block. 350 | $replacementContent .= $newContent; 351 | 352 | // Contiguous token changes can be combined. 353 | if (isset($changedTokens[$stackPtr + 1])) { 354 | continue; 355 | } 356 | 357 | $codeActionEdits[] = array( 358 | 'range' => array( 359 | 'startLine' => $startLine, 360 | 'startCharacter' => $startCharacter, 361 | 'endLine' => $endLine, 362 | 'endCharacter' => $endCharacter 363 | ), 364 | 'newContent' => $replacementContent 365 | ); 366 | // Clear the replacement after we've added it. 367 | $replacementContent = null; 368 | } 369 | 370 | return $codeActionEdits; 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /assets/phpcs-integration/Extension/Fixer.php: -------------------------------------------------------------------------------- 1 | textEditChangeset) { 35 | $this->textEditTokens[$stackPtr] = $stackPtr; 36 | } 37 | 38 | return $ret; 39 | } 40 | 41 | public function revertToken($stackPtr) 42 | { 43 | $ret = parent::revertToken($stackPtr); 44 | if (!$ret) { 45 | return $ret; 46 | } 47 | 48 | if (!$this->textEditChangeset) { 49 | unset($this->textEditTokens[$stackPtr]); 50 | } 51 | 52 | return $ret; 53 | } 54 | 55 | public function beginChangeset() 56 | { 57 | $ret = parent::beginChangeset(); 58 | if (!isset($ret) || $ret) { 59 | $this->textEditChangeset = true; 60 | } 61 | 62 | return $ret; 63 | } 64 | 65 | public function endChangeset() 66 | { 67 | $this->textEditChangeset = false; 68 | return parent::endChangeset(); 69 | } 70 | 71 | public function rollbackChangeset() 72 | { 73 | $this->textEditChangeset = false; 74 | return parent::rollbackChangeset(); 75 | } 76 | 77 | /** 78 | * Fetches all of the tokens that have been modified. 79 | * 80 | * @return array 81 | */ 82 | public function getTextEditTokens() 83 | { 84 | $tokens = array(); 85 | foreach ($this->textEditTokens as $stackPtr) { 86 | $tokens[$stackPtr] = $this->getTokenContent($stackPtr); 87 | } 88 | 89 | // Sort the actions so that finding contiguous blocks is trivial. 90 | ksort($tokens); 91 | 92 | return $tokens; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /assets/phpcs-integration/Handlers/CodeAction.php: -------------------------------------------------------------------------------- 1 | code; 25 | 26 | // Find the token in the file using the VS Code position. 27 | $stackPtr = $file->getStackPtrForPosition($data->line, $data->character, true); 28 | if (!isset($stackPtr)) { 29 | return false; 30 | } 31 | 32 | // Find the token that we're trying to fix. 33 | $token = $file->getToken($stackPtr); 34 | 35 | // Find the problem message we're trying to fix. 36 | if (!isset($report['messages'][$token['line']][$token['column']])) { 37 | return false; 38 | } 39 | $messages = $report['messages'][$token['line']][$token['column']]; 40 | foreach ($messages as $message) { 41 | // Only operate on the specific problem we're looking for. 42 | if ($message['source'] !== $problemSource) { 43 | continue; 44 | } 45 | 46 | // If it isn't fixable then we don't need to worry about it. 47 | if (!$message['fixable']) { 48 | continue; 49 | } 50 | 51 | // Fix the specific action and render the edits. 52 | $edits = $file->fixCodeAction($stackPtr, $problemSource); 53 | if (empty($edits)) { 54 | continue; 55 | } 56 | 57 | echo json_encode( 58 | array( 59 | 'filename' => $report['filename'], 60 | 'textEdits' => $edits 61 | ), 62 | JSON_UNESCAPED_LINE_TERMINATORS 63 | ); 64 | // Ensure multiple files are separated by a comma. 65 | echo ','; 66 | 67 | return true; 68 | } 69 | 70 | return false; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /assets/phpcs-integration/Handlers/Diagnostic.php: -------------------------------------------------------------------------------- 1 | $columns) { 28 | foreach ($columns as $column => $messages) { 29 | $stackPtr = $file->getStackPtrForPosition($line, $column); 30 | if (!isset($stackPtr)) { 31 | continue; 32 | } 33 | $token = $file->getToken($stackPtr); 34 | 35 | foreach ($messages as $message) { 36 | // When fixable create a code action object according to the VS Code CodeAction schema. 37 | if ($message['fixable']) { 38 | $codeAction = array( 39 | 'title' => 'Fix ' . $message['source'], 40 | 'kind' => 'quickfix', 41 | // The index can be used to associate the code action in VS Code. 42 | 'diagnostic' => $diagnosticIndex 43 | ); 44 | $codeActions[] = $codeAction; 45 | } 46 | 47 | // Create a diagnostic object according to the VS Code Diagnostic schema. 48 | $diagnostic = array( 49 | 'code' => $message['source'], 50 | 'message' => $message['message'], 51 | 'range' => $token['vscode_range'], 52 | // Enum Values: DiagnosticSeverity.Error : DiagnosticSeverity.Warning 53 | 'severity' => strtolower($message['type']) === 'error' ? 0 : 1, 54 | 'source' => 'PHP_CodeSniffer' 55 | ); 56 | $diagnostics[$diagnosticIndex++] = $diagnostic; 57 | } 58 | } 59 | } 60 | 61 | echo json_encode( 62 | array( 63 | 'filename' => $report['filename'], 64 | 'diagnostics' => $diagnostics, 65 | 'codeActions' => $codeActions 66 | ), 67 | JSON_UNESCAPED_LINE_TERMINATORS 68 | ); 69 | // Ensure multiple files are separated by a comma. 70 | echo ','; 71 | 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /assets/phpcs-integration/Handlers/Format.php: -------------------------------------------------------------------------------- 1 | start)) { 25 | // Find the token in the file using the VS Code position. 26 | $startToken = $file->getStackPtrForPosition($data->start->line, $data->start->character, true); 27 | if (!isset($startToken)) { 28 | return false; 29 | } 30 | } 31 | $endToken = null; 32 | if (isset($data->end)) { 33 | // Find the token in the file using the VS Code position. 34 | $endToken = $file->getStackPtrForPosition($data->end->line, $data->end->character, true); 35 | if (!isset($endToken)) { 36 | return false; 37 | } 38 | } 39 | 40 | // Format the given range. 41 | $newContent = $file->formatRange($startToken, $endToken); 42 | 43 | // We have nothing to return if the file wasn't modified. 44 | if ($newContent === false) { 45 | return false; 46 | } 47 | 48 | echo json_encode( 49 | array( 50 | 'filename' => $report['filename'], 51 | 'content' => $newContent 52 | ), 53 | JSON_UNESCAPED_LINE_TERMINATORS 54 | ); 55 | // Ensure multiple files are separated by a comma. 56 | echo ','; 57 | 58 | return true; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /assets/phpcs-integration/Handlers/Handler.php: -------------------------------------------------------------------------------- 1 | getVSCodeInput(); 52 | 53 | // Make sure that we are running the correct version of the integration package. 54 | if ($input->version !== PHPCS_INTEGRATION_VERSION) { 55 | $errorMessage = 'The extension expected version ' 56 | . PHPCS_INTEGRATION_VERSION 57 | . ' of the integration files. Current Version: ' 58 | . $input->version . PHP_EOL; 59 | throw new \InvalidArgumentException($errorMessage); 60 | } 61 | 62 | // Use the handler to process the report. 63 | $handler = $this->getHandler($input->type); 64 | return $handler->execute($report, $phpcsFile, $input->data); 65 | } 66 | 67 | /** 68 | * Generates the final report. 69 | * 70 | * @param string $cachedData The result from running generateFileReport on each file in the request. 71 | * @param int $totalFiles Total nunber of files checked. 72 | * @param int $totalErrors Total number of errors. 73 | * @param int $totalWarnings Total number of warnings. 74 | * @param int $totalFixable Total number of fixable problems. 75 | * @param bool $showSources Whether or not we should show sources in the report. 76 | * @param int $width The maximum allowed line width for the report. 77 | * @param bool $interactive Indicates whether or not the report is being generated interactively. 78 | * @param bool $toScreen Indicates whether or not the report is being printed to the screen. 79 | */ 80 | public function generate( 81 | $cachedData, 82 | $totalFiles, 83 | $totalErrors, 84 | $totalWarnings, 85 | $totalFixable, 86 | $showSources = false, 87 | $width = 80, 88 | $interactive = false, 89 | $toScreen = true 90 | ) { 91 | // Remove the trailing comma that every file adds to the end of their report. 92 | echo '{"files":['; 93 | echo rtrim($cachedData, ","); 94 | echo ']}' . PHP_EOL; 95 | } 96 | 97 | /** 98 | * Gets the handler class for this report. 99 | * 100 | * @param string $reportType The type of the report we are running. 101 | * @return Handler 102 | * @throws \InvalidArgumentException The handler could not be found. 103 | */ 104 | protected function getHandler($reportType) 105 | { 106 | // Find the handler class file that should power this report. 107 | $report = '\\ObliviousHarmony\\VSCodePHPCSIntegration\\Handlers\\' . $reportType; 108 | if (!\class_exists($report)) { 109 | throw new \InvalidArgumentException('Handler "' . $report . '" could be found'); 110 | } 111 | 112 | return new $report(); 113 | } 114 | 115 | /** 116 | * Reads data from the VS Code environment variable. 117 | * 118 | * @return \stdClass|null 119 | * @throws \InvalidArgumentException The environemnt variable is invalid. 120 | */ 121 | protected function getVSCodeInput() 122 | { 123 | if (empty($_SERVER['PHPCS_VSCODE_INPUT'])) { 124 | return null; 125 | } 126 | $data = json_decode($_SERVER['PHPCS_VSCODE_INPUT']); 127 | if (empty($data)) { 128 | throw new \InvalidArgumentException('The "PHPCS_VSCODE_INPUT" environment variable is invalid.'); 129 | } 130 | 131 | return $data; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obliviousharmony/vscode-phpcs-integration", 3 | "description": "The custom PHPCS integration for the obliviousharmony.vscode-php-codesniffer VS Code extension.", 4 | "homepage": "https://github.com/ObliviousHarmony/vscode-php-codesniffer", 5 | "license": "GPL-2.0-or-later", 6 | "authors": [ 7 | { 8 | "name": "Christopher Allford", 9 | "homepage": "https://github.com/ObliviousHarmony" 10 | } 11 | ], 12 | "keywords": [ 13 | "phpcs", 14 | "phpcbf", 15 | "vscode", 16 | "vscode-extension", 17 | "vscode-phpcs", 18 | "vscode-phpcbf" 19 | ], 20 | "readme": "assets/phpcs-integration/README.md", 21 | "prefer-stable": true, 22 | "minimum-stability": "dev", 23 | "require-dev": { 24 | "squizlabs/php_codesniffer": "^3.1" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "ObliviousHarmony\\VSCodePHPCSIntegration\\": "assets/phpcs-integration" 29 | } 30 | }, 31 | "scripts": { 32 | "lint": "phpcs --standard=psr12 -sp assets", 33 | "lint:fix": "phpcbf --standard=psr12 -sp assets" 34 | } 35 | } 36 | --------------------------------------------------------------------------------