diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..77b6c56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,28 @@ +# +# Exclude these files from release archives. +# This will also make them unavailable when using Composer with `--prefer-dist`. +# If you develop for this repo using Composer, use `--prefer-source`. +# https://www.reddit.com/r/PHP/comments/2jzp6k/i_dont_need_your_tests_in_my_production +# https://blog.madewithlove.be/post/gitattributes/ +# +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/phpcs.xml.dist export-ignore +/phpunit.xml.dist export-ignore +/phpunit-bootstrap.php export-ignore +/NormalizedArrays/Tests/ export-ignore +/Universal/Tests/ export-ignore + +# +# Auto detect text files and perform LF normalization +# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ +# +* text=auto + +# +# The above will handle all files NOT found below +# +*.md text +*.php text +*.inc text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de278d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build/ +vendor/ +/composer.lock +/.phpcs.xml +/phpcs.xml +/phpunit.xml +/.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..aeff6a6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,142 @@ +dist: trusty + +language: php + +## Cache composer and apt downloads. +cache: + apt: true + directories: + # Cache directory for older Composer versions. + - $HOME/.composer/cache/files + # Cache directory for more recent Composer versions. + - $HOME/.cache/composer/files + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - 7.3 + +env: + jobs: + # `master` + - PHPCS_VERSION="dev-master" LINT=1 + # Lowest supported PHPCS version. + - PHPCS_VERSION="3.3.1" + +# Define the stages used. +# For non-PRs, only the sniff and quicktest stages are run. +# For pull requests and merges, the full script is run (skipping quicktest). +# Note: for pull requests, "develop" is the base branch name. +# See: https://docs.travis-ci.com/user/conditions-v1 +stages: + - name: sniff + - name: quicktest + if: type = push AND branch NOT IN (master, develop) + - name: test + if: branch IN (master, develop) + +jobs: + fast_finish: true + + include: + #### SNIFF STAGE #### + - stage: sniff + php: 7.4 + env: PHPCS_VERSION="dev-master" + addons: + apt: + packages: + - libxml2-utils + script: + # Validate the composer.json file. + # @link https://getcomposer.org/doc/03-cli.md#validate + - composer validate --no-check-all --strict + + # Check the code style of the code base. + - composer checkcs + + # Validate the xml files. + # @link http://xmlsoft.org/xmllint.html + - xmllint --noout --schema ./vendor/squizlabs/php_codesniffer/phpcs.xsd ./NormalizedArrays/ruleset.xml + - xmllint --noout --schema ./vendor/squizlabs/php_codesniffer/phpcs.xsd ./Universal/ruleset.xml + + # Check the code-style consistency of the xml files. + - diff -B ./NormalizedArrays/ruleset.xml <(xmllint --format "./NormalizedArrays/ruleset.xml") + - diff -B ./Universal/ruleset.xml <(xmllint --format "./Universal/ruleset.xml") + + # Check that the sniffs available are feature complete. + - composer check-complete + + #### QUICK TEST STAGE #### + # This is a much quicker test which only runs the unit tests and linting against the low/high + # supported PHP/PHPCS combinations. + - stage: quicktest + php: 7.4 + env: PHPCS_VERSION="dev-master" LINT=1 + - php: 7.3 + env: PHPCS_VERSION="3.3.1" + + - php: 5.4 + env: PHPCS_VERSION="dev-master" LINT=1 + - php: 5.4 + env: PHPCS_VERSION="3.3.1" + + #### TEST STAGE #### + # Additional builds to prevent issues with PHPCS versions incompatible with certain PHP versions. + - stage: test + php: 7.4 + env: PHPCS_VERSION="dev-master" LINT=1 + # PHPCS is only compatible with PHP 7.4 as of version 3.5.0. + - php: 7.4 + env: PHPCS_VERSION="3.5.0" + + - php: "nightly" + env: PHPCS_VERSION="n/a" LINT=1 + + allow_failures: + # Allow failures for unstable builds. + - php: "nightly" + + +before_install: + # Speed up build time by disabling Xdebug when its not needed. + - phpenv config-rm xdebug.ini || echo 'No xdebug config.' + + - export XMLLINT_INDENT=" " + + # On stable PHPCS versions, allow for PHP deprecation notices. + # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore. + - | + if [[ "$TRAVIS_BUILD_STAGE_NAME" != "Sniff" && $PHPCS_BRANCH != "dev-master" && "$PHPCS_VERSION" != "n/a" ]]; then + echo 'error_reporting = E_ALL & ~E_DEPRECATED' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini + fi + + +install: + # Set up test environment using Composer. + - | + if [[ $PHPCS_VERSION != "n/a" ]]; then + composer require --no-update --no-scripts squizlabs/php_codesniffer:${PHPCS_VERSION} + fi + - | + if [[ "$TRAVIS_BUILD_STAGE_NAME" == "Sniff" || $PHPCS_VERSION == "n/a" ]]; then + # The sniff stage doesn't run the unit tests, so no need for PHPUnit. + # The build on nightly also doesn't run the tests (yet). + composer remove --dev phpunit/phpunit --no-update --no-scripts + fi + + # --prefer-dist will allow for optimal use of the travis caching ability. + # The Composer PHPCS plugin takes care of setting the installed_paths for PHPCS. + - composer install --prefer-dist --no-suggest + + +script: + # Lint PHP files against parse errors. + - if [[ "$LINT" == "1" ]]; then composer lint; fi + + # Run the tests. + - if [[ $PHPCS_VERSION != "n/a" ]]; then composer test; fi diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/NormalizedArrays/Docs/Arrays/ArrayBraceSpacingStandard.xml b/NormalizedArrays/Docs/Arrays/ArrayBraceSpacingStandard.xml new file mode 100644 index 0000000..b09af7b --- /dev/null +++ b/NormalizedArrays/Docs/Arrays/ArrayBraceSpacingStandard.xml @@ -0,0 +1,93 @@ + + + + + + + + + + (1, 2); + ]]> + + + + + + + + + + + ); + +$args = [ ]; + ]]> + + + + + + + + + + + 1, 2 ); + +$args = [ 1, 2 ]; + ]]> + + + + + + + + + 1, + 2 +); + +$args = [ + 1, + 2 +]; + ]]> + + + + + +]; + ]]> + + + diff --git a/NormalizedArrays/Docs/Arrays/CommaAfterLastStandard.xml b/NormalizedArrays/Docs/Arrays/CommaAfterLastStandard.xml new file mode 100644 index 0000000..321608e --- /dev/null +++ b/NormalizedArrays/Docs/Arrays/CommaAfterLastStandard.xml @@ -0,0 +1,39 @@ + + + no comma after the last array item. + + However, for multi-line arrays, there should be a comma after the last array item. + ]]> + + + + + + + , ); + ]]> + + + + + 'foo', + 2 => 'bar', +]; + ]]> + + + 'foo', + 2 => 'bar' +]; + ]]> + + + diff --git a/NormalizedArrays/Sniffs/Arrays/ArrayBraceSpacingSniff.php b/NormalizedArrays/Sniffs/Arrays/ArrayBraceSpacingSniff.php new file mode 100644 index 0000000..bc0f0c5 --- /dev/null +++ b/NormalizedArrays/Sniffs/Arrays/ArrayBraceSpacingSniff.php @@ -0,0 +1,294 @@ +keywordSpacing !== false) { + $this->keywordSpacing = \max((int) $this->keywordSpacing, 0); + } + + if ($this->spacesSingleLine !== false) { + $this->spacesSingleLine = \max((int) $this->spacesSingleLine, 0); + } + + if ($this->spacesMultiLine !== false && $this->spacesMultiLine !== 'newline') { + $this->spacesMultiLine = \max((int) $this->spacesMultiLine, 0); + } + + if ($this->spacesWhenEmpty !== false && $this->spacesWhenEmpty !== 'newline') { + $this->spacesWhenEmpty = \max((int) $this->spacesWhenEmpty, 0); + } + + if ($this->keywordSpacing === false + && $this->spacesSingleLine === false + && $this->spacesMultiLine === false + && $this->spacesWhenEmpty === false + ) { + // Nothing to do. Why was the sniff turned on at all ? + return; + } + + $openClose = Arrays::getOpenClose($phpcsFile, $stackPtr); + if ($openClose === false) { + // Short list or real square brackets. + return; + } + + $tokens = $phpcsFile->getTokens(); + $opener = $openClose['opener']; + $closer = $openClose['closer']; + + /* + * Check the spacing between the array keyword and the open parenthesis for long arrays. + */ + if ($tokens[$stackPtr]['code'] === \T_ARRAY && $this->keywordSpacing !== false) { + $error = 'There should be %s between the "array" keyword and the open parenthesis. Found: %s'; + $code = 'SpaceAfterKeyword'; + + SpacesFixer::checkAndFix( + $phpcsFile, + $stackPtr, + $opener, + $this->keywordSpacing, + $error, + $code, + 'error', + 0, + 'Space between array keyword and open brace' + ); + } + + /* + * Check for empty arrays. + */ + $nextNonWhiteSpace = $phpcsFile->findNext(\T_WHITESPACE, ($opener + 1), null, true); + if ($nextNonWhiteSpace === $closer) { + if ($this->spacesWhenEmpty === false) { + // Check was turned off. + return; + } + + $error = 'There should be %s between the array opener and closer for an empty array. Found: %s'; + $code = 'EmptyArraySpacing'; + + SpacesFixer::checkAndFix( + $phpcsFile, + $opener, + $closer, + $this->spacesWhenEmpty, + $error, + $code, + 'error', + 0, + 'Space between open and close brace for an empty array' + ); + + return; + } + + /* + * Check non-empty arrays. + */ + if ($tokens[$opener]['line'] === $tokens[$closer]['line']) { + // Single line array. + if ($this->spacesSingleLine === false) { + // Check was turned off. + return; + } + + $error = 'Expected %s after the array opener in a single line array. Found: %s'; + $code = 'SpaceAfterArrayOpenerSingleLine'; + + SpacesFixer::checkAndFix( + $phpcsFile, + $opener, + $phpcsFile->findNext(\T_WHITESPACE, ($opener + 1), null, true), + $this->spacesSingleLine, + $error, + $code, + 'error', + 0, + 'Space after array opener, single line array' + ); + + $error = 'Expected %s before the array closer in a single line array. Found: %s'; + $code = 'SpaceBeforeArrayCloserSingleLine'; + + SpacesFixer::checkAndFix( + $phpcsFile, + $closer, + $phpcsFile->findPrevious(\T_WHITESPACE, ($closer - 1), null, true), + $this->spacesSingleLine, + $error, + $code, + 'error', + 0, + 'Space before array closer, single line array' + ); + + return; + } + + // Multi-line array. + if ($this->spacesMultiLine === false) { + // Check was turned off. + return; + } + + $error = 'Expected %s after the array opener in a multi line array. Found: %s'; + $code = 'SpaceAfterArrayOpenerMultiLine'; + + SpacesFixer::checkAndFix( + $phpcsFile, + $opener, + $phpcsFile->findNext(\T_WHITESPACE, ($opener + 1), null, true), + $this->spacesMultiLine, + $error, + $code, + 'error', + 0, + 'Space after array opener, multi-line array' + ); + + $error = 'Expected %s before the array closer in a multi line array. Found: %s'; + $code = 'SpaceBeforeArrayCloserMultiLine'; + + SpacesFixer::checkAndFix( + $phpcsFile, + $closer, + $phpcsFile->findPrevious(\T_WHITESPACE, ($closer - 1), null, true), + $this->spacesMultiLine, + $error, + $code, + 'error', + 0, + 'Space before array closer, multi-line array' + ); + } +} diff --git a/NormalizedArrays/Sniffs/Arrays/CommaAfterLastSniff.php b/NormalizedArrays/Sniffs/Arrays/CommaAfterLastSniff.php new file mode 100644 index 0000000..2adf4a1 --- /dev/null +++ b/NormalizedArrays/Sniffs/Arrays/CommaAfterLastSniff.php @@ -0,0 +1,210 @@ + true, + 'forbid' => true, + 'skip' => true, + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 1.0.0 + * + * @return array + */ + public function register() + { + return [ + \T_ARRAY, + \T_OPEN_SHORT_ARRAY, + \T_OPEN_SQUARE_BRACKET, + ]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + // Validate the property input. Invalid values will result in the check being skipped. + if (isset($this->validValues[$this->singleLine]) === false) { + $this->singleLine = 'skip'; + } + if (isset($this->validValues[$this->multiLine]) === false) { + $this->multiLine = 'skip'; + } + + $openClose = Arrays::getOpenClose($phpcsFile, $stackPtr); + if ($openClose === false) { + // Short list, real square bracket, live coding or parse error. + return; + } + + $tokens = $phpcsFile->getTokens(); + $opener = $openClose['opener']; + $closer = $openClose['closer']; + + $action = $this->singleLine; + $phrase = 'single-line'; + $errorCode = 'SingleLine'; + if ($tokens[$opener]['line'] !== $tokens[$closer]['line']) { + $action = $this->multiLine; + $phrase = 'multi-line'; + $errorCode = 'MultiLine'; + } + + if ($action === 'skip') { + // Nothing to do. + return; + } + + $lastNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($closer - 1), $opener, true); + if ($lastNonEmpty === false || $lastNonEmpty === $opener) { + // Bow out: empty array. + return; + } + + $isComma = ($tokens[$lastNonEmpty]['code'] === \T_COMMA); + + $phpcsFile->recordMetric( + $stackPtr, + \ucfirst($phrase) . ' array - comma after last item', + ($isComma === true ? 'yes' : 'no') + ); + + switch ($action) { + case 'enforce': + if ($isComma === true) { + return; + } + + $error = 'There should be a comma after the last array item in a %s array.'; + $errorCode = 'Missing' . $errorCode; + $data = [$phrase]; + $fix = $phpcsFile->addFixableError($error, $lastNonEmpty, $errorCode, $data); + if ($fix === true) { + $extraContent = ','; + + if ($tokens[$lastNonEmpty]['code'] === \T_END_HEREDOC + || $tokens[$lastNonEmpty]['code'] === \T_END_NOWDOC + ) { + // Prevent parse errors in PHP < 7.3 which doesn't support flexible heredoc/nowdoc. + $extraContent = $phpcsFile->eolChar . $extraContent; + } + + $phpcsFile->fixer->addContent($lastNonEmpty, $extraContent); + } + + return; + + case 'forbid': + if ($isComma === false) { + return; + } + + $error = 'A comma after the last array item in a %s array is not allowed.'; + $errorCode = 'Found' . $errorCode; + $data = [$phrase]; + $fix = $phpcsFile->addFixableError($error, $lastNonEmpty, $errorCode, $data); + if ($fix === true) { + $start = $lastNonEmpty; + $end = $lastNonEmpty; + + // Make sure we're not leaving a superfluous blank line behind. + $prevNonWhitespace = $phpcsFile->findPrevious(\T_WHITESPACE, ($lastNonEmpty - 1), $opener, true); + $nextNonWhitespace = $phpcsFile->findNext(\T_WHITESPACE, ($lastNonEmpty + 1), ($closer + 1), true); + if ($prevNonWhitespace !== false + && $tokens[$prevNonWhitespace]['line'] < $tokens[$lastNonEmpty]['line'] + && $nextNonWhitespace !== false + && $tokens[$nextNonWhitespace]['line'] > $tokens[$lastNonEmpty]['line'] + ) { + $start = ($prevNonWhitespace + 1); + } + + $phpcsFile->fixer->beginChangeset(); + for ($i = $start; $i <= $end; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->endChangeset(); + } + + return; + } + } +} diff --git a/NormalizedArrays/Tests/Arrays/ArrayBraceSpacingUnitTest.inc b/NormalizedArrays/Tests/Arrays/ArrayBraceSpacingUnitTest.inc new file mode 100644 index 0000000..ed59db9 --- /dev/null +++ b/NormalizedArrays/Tests/Arrays/ArrayBraceSpacingUnitTest.inc @@ -0,0 +1,211 @@ + => + */ + public function getErrorList() + { + return [ + 11 => 1, + 12 => 1, + 13 => 1, + 18 => 1, + 22 => 1, + 23 => 1, + 25 => 1, + 43 => 1, + 44 => 1, + 52 => 1, + 53 => 1, + 54 => 1, + 57 => 1, + 65 => 1, + 66 => 1, + 67 => 1, + 68 => 1, + 92 => 2, + 93 => 2, + 94 => 1, + 101 => 2, + 102 => 2, + 103 => 2, + 104 => 2, + 129 => 1, + 130 => 1, + 137 => 1, + 139 => 1, + 150 => 1, + 153 => 1, + 155 => 1, + 164 => 1, + 173 => 1, + 176 => 1, + 178 => 1, + 183 => 1, + 185 => 1, + 187 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.inc b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.inc new file mode 100644 index 0000000..8615ee9 --- /dev/null +++ b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.inc @@ -0,0 +1,177 @@ +method_name( array( + 'phrase' => << 'value' , + 2 => [ + 'a' => 'value' ,// phpcs:disable Standard.Category.Sniff - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( + 1 + ), + 'c' => apply_filters( 'filter', $input, $var ) + ], + 3 => apply_filters( 'filter', $input, $var )/* phpcs:ignore Standard.Category.Sniff */ +); + +$missing = array( + 'first', + 'second' + //'third', + ); + +$missingNowdoc = function_call()->method_name( array( + 'phrase' => <<<'EOD' +Here comes some text. +EOD +) ); + +/* + * Test forbidding a comma after the last array item. + */ +// phpcs:set NormalizedArrays.Arrays.CommaAfterLast singleLine forbid + +$good = array( 1, 2, 3 ); +$good = [ 'a', 'b', 'c' ]; + +$found = array( 1, 2, 3, ); +$found = [ 'a', 'b', 'c', ]; + +// phpcs:set NormalizedArrays.Arrays.CommaAfterLast multiLine forbid + +$good = array( + 1, + 3 +); +$good = [ + 'a', + 'c' +]; + +$goodNowdoc = function_call()->method_name( array( + 'phrase' => <<<'EOD' +Here comes some text. +EOD +) ); + +$found = array( + 1, + 3,/* Comment. */ +); +$found = [ + 'a', + 'c', +]; + +$foundInNested = array( + 1 => 'value' , + 2 => [ + 'a' => 'value' ,// phpcs:disable Standard.Category.Sniff - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( + 1, + ), + 'c' => apply_filters( 'filter', $input, $var ), + ], + 3 => apply_filters( 'filter', $input, $var ), /* phpcs:ignore Standard.Category.Sniff */ +); + +$foundHeredoc = function_call()->method_name( array( + 'phrase' => <<<"EOD" +Here comes some text. +EOD +, +) ); + +$foundHeredoc = function_call()->method_name( array( + 'phrase' => <<<"EOD" +Here comes some text. +EOD +, /*comment*/ +) ); + +// Reset the properties to the defaults. +// phpcs:set NormalizedArrays.Arrays.CommaAfterLast singleLine forbid +// phpcs:set NormalizedArrays.Arrays.CommaAfterLast multiLine enforce + +/* + * Test live coding. This should be the last test in the file. + */ +// Intentional parse error. +$ignore = array( diff --git a/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.inc.fixed b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.inc.fixed new file mode 100644 index 0000000..d06573c --- /dev/null +++ b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.inc.fixed @@ -0,0 +1,177 @@ +method_name( array( + 'phrase' => << 'value' , + 2 => [ + 'a' => 'value' ,// phpcs:disable Standard.Category.Sniff - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( + 1, + ), + 'c' => apply_filters( 'filter', $input, $var ), + ], + 3 => apply_filters( 'filter', $input, $var ),/* phpcs:ignore Standard.Category.Sniff */ +); + +$missing = array( + 'first', + 'second', + //'third', + ); + +$missingNowdoc = function_call()->method_name( array( + 'phrase' => <<<'EOD' +Here comes some text. +EOD +, +) ); + +/* + * Test forbidding a comma after the last array item. + */ +// phpcs:set NormalizedArrays.Arrays.CommaAfterLast singleLine forbid + +$good = array( 1, 2, 3 ); +$good = [ 'a', 'b', 'c' ]; + +$found = array( 1, 2, 3 ); +$found = [ 'a', 'b', 'c' ]; + +// phpcs:set NormalizedArrays.Arrays.CommaAfterLast multiLine forbid + +$good = array( + 1, + 3 +); +$good = [ + 'a', + 'c' +]; + +$goodNowdoc = function_call()->method_name( array( + 'phrase' => <<<'EOD' +Here comes some text. +EOD +) ); + +$found = array( + 1, + 3/* Comment. */ +); +$found = [ + 'a', + 'c' +]; + +$foundInNested = array( + 1 => 'value' , + 2 => [ + 'a' => 'value' ,// phpcs:disable Standard.Category.Sniff - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( + 1 + ), + 'c' => apply_filters( 'filter', $input, $var ) + ], + 3 => apply_filters( 'filter', $input, $var ) /* phpcs:ignore Standard.Category.Sniff */ +); + +$foundHeredoc = function_call()->method_name( array( + 'phrase' => <<<"EOD" +Here comes some text. +EOD +) ); + +$foundHeredoc = function_call()->method_name( array( + 'phrase' => <<<"EOD" +Here comes some text. +EOD + /*comment*/ +) ); + +// Reset the properties to the defaults. +// phpcs:set NormalizedArrays.Arrays.CommaAfterLast singleLine forbid +// phpcs:set NormalizedArrays.Arrays.CommaAfterLast multiLine enforce + +/* + * Test live coding. This should be the last test in the file. + */ +// Intentional parse error. +$ignore = array( diff --git a/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.php b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.php new file mode 100644 index 0000000..31d3463 --- /dev/null +++ b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.php @@ -0,0 +1,63 @@ + => + */ + public function getErrorList() + { + return [ + 52 => 1, + 53 => 1, + 75 => 1, + 79 => 1, + 87 => 1, + 89 => 1, + 91 => 1, + 96 => 1, + 103 => 1, + 114 => 1, + 115 => 1, + 136 => 1, + 140 => 1, + 148 => 1, + 150 => 1, + 152 => 1, + 159 => 1, + 166 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/NormalizedArrays/ruleset.xml b/NormalizedArrays/ruleset.xml new file mode 100644 index 0000000..ce35adb --- /dev/null +++ b/NormalizedArrays/ruleset.xml @@ -0,0 +1,5 @@ + + + + A ruleset for PHP_CodeSniffer to check arrays for normalized format. + diff --git a/Universal/Docs/Arrays/DuplicateArrayKeyStandard.xml b/Universal/Docs/Arrays/DuplicateArrayKeyStandard.xml new file mode 100644 index 0000000..f940a3f --- /dev/null +++ b/Universal/Docs/Arrays/DuplicateArrayKeyStandard.xml @@ -0,0 +1,40 @@ + + + + + + + 'foo' => 22, + 'bar' => 25, + 'baz' => 28, +); + +$args = array( + 22, + 25, + 2 => 28, +); + ]]> + + + 'foo' => 22, + 'bar' => 25, + 'bar' => 28, +); + +$args = array( + 22, + 25, + 1 => 28, +); + ]]> + + + diff --git a/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml b/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml new file mode 100644 index 0000000..f8fd854 --- /dev/null +++ b/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml @@ -0,0 +1,36 @@ + + + + + + + 'foo' => 22, + 'bar' => 25, +); + +$args = array( + 0 => 22, + 1 => 25, +); + ]]> + + + 22, + 25, +); + +$args = array( + 'foo' => 22, + 12 => 25, +); + + ]]> + + + diff --git a/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml b/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml new file mode 100644 index 0000000..e28f699 --- /dev/null +++ b/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml @@ -0,0 +1,27 @@ + + + + + + + 'foo' => 22, + 'bar' => 25, +); + +$args = array(22, 25); + ]]> + + + 22, + 25, +); + ]]> + + + diff --git a/Universal/Docs/ControlStructures/IfElseDeclarationStandard.xml b/Universal/Docs/ControlStructures/IfElseDeclarationStandard.xml new file mode 100644 index 0000000..fe3cb9a --- /dev/null +++ b/Universal/Docs/ControlStructures/IfElseDeclarationStandard.xml @@ -0,0 +1,34 @@ + + + + + + + + +elseif ($bar) { + $var = 2; +} +else { + $var = 3; +} + ]]> + + + elseif ($bar) { + $var = 2; +} else { + $var = 3; +} + ]]> + + + diff --git a/Universal/Docs/Lists/DisallowLongListSyntaxStandard.xml b/Universal/Docs/Lists/DisallowLongListSyntaxStandard.xml new file mode 100644 index 0000000..d1b0ea6 --- /dev/null +++ b/Universal/Docs/Lists/DisallowLongListSyntaxStandard.xml @@ -0,0 +1,19 @@ + + + + + + + [$a, $b] = $array; + ]]> + + + list($a, $b) = $array; + ]]> + + + diff --git a/Universal/Docs/Lists/DisallowShortListSyntaxStandard.xml b/Universal/Docs/Lists/DisallowShortListSyntaxStandard.xml new file mode 100644 index 0000000..db6e66e --- /dev/null +++ b/Universal/Docs/Lists/DisallowShortListSyntaxStandard.xml @@ -0,0 +1,19 @@ + + + + + + + list($a, $b) = $array; + ]]> + + + [$a, $b] = $array; + ]]> + + + diff --git a/Universal/Docs/Namespaces/DisallowCurlyBraceSyntaxStandard.xml b/Universal/Docs/Namespaces/DisallowCurlyBraceSyntaxStandard.xml new file mode 100644 index 0000000..91eabf4 --- /dev/null +++ b/Universal/Docs/Namespaces/DisallowCurlyBraceSyntaxStandard.xml @@ -0,0 +1,24 @@ + + + + + + + + ; + +// Code + ]]> + + + { + // Code. +} + ]]> + + + diff --git a/Universal/Docs/Namespaces/EnforceCurlyBraceSyntaxStandard.xml b/Universal/Docs/Namespaces/EnforceCurlyBraceSyntaxStandard.xml new file mode 100644 index 0000000..411a102 --- /dev/null +++ b/Universal/Docs/Namespaces/EnforceCurlyBraceSyntaxStandard.xml @@ -0,0 +1,24 @@ + + + + + + + + { + // Code. +} + ]]> + + + ; + +// Code + ]]> + + + diff --git a/Universal/Docs/Namespaces/OneDeclarationPerFileStandard.xml b/Universal/Docs/Namespaces/OneDeclarationPerFileStandard.xml new file mode 100644 index 0000000..5d5de02 --- /dev/null +++ b/Universal/Docs/Namespaces/OneDeclarationPerFileStandard.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + namespace Vendor\Project\Sub\B { +} + ]]> + + + diff --git a/Universal/Docs/UseStatements/DisallowUseClassStandard.xml b/Universal/Docs/UseStatements/DisallowUseClassStandard.xml new file mode 100644 index 0000000..00b69ba --- /dev/null +++ b/Universal/Docs/UseStatements/DisallowUseClassStandard.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/Universal/Docs/UseStatements/DisallowUseConstStandard.xml b/Universal/Docs/UseStatements/DisallowUseConstStandard.xml new file mode 100644 index 0000000..8a331a0 --- /dev/null +++ b/Universal/Docs/UseStatements/DisallowUseConstStandard.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/Universal/Docs/UseStatements/DisallowUseFunctionStandard.xml b/Universal/Docs/UseStatements/DisallowUseFunctionStandard.xml new file mode 100644 index 0000000..fc327bc --- /dev/null +++ b/Universal/Docs/UseStatements/DisallowUseFunctionStandard.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php b/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php new file mode 100644 index 0000000..9f46700 --- /dev/null +++ b/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php @@ -0,0 +1,156 @@ +keysSeen = []; + $this->currentMaxIntKey = -1; + + parent::processArray($phpcsFile); + } + + /** + * Process the tokens in an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the "key" part of + * an array item. + * @param int $endPtr The stack pointer to the last token in the "key" part of + * an array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return void + */ + public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr) + { + $key = $this->getActualArrayKey($phpcsFile, $startPtr, $endPtr); + + if (isset($key) === false) { + // Key could not be determined. + return; + } + + $integerKey = \is_int($key); + + /* + * Check if we've seen it before. + */ + if (isset($this->keysSeen[$key]) === true) { + $firstSeen = $this->keysSeen[$key]; + $firstNonEmptyFirstSeen = $phpcsFile->findNext(Tokens::$emptyTokens, $firstSeen['ptr'], null, true); + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); + + $data = [ + ($integerKey === true) ? 'integer' : 'string', + $key, + $firstSeen['item'], + $this->tokens[$firstNonEmptyFirstSeen]['line'], + ]; + + $phpcsFile->addError( + 'Duplicate array key found. The value will be overwritten.' + . ' The %s array key "%s" was first seen for array item %d on line %d', + $firstNonEmpty, + 'Found', + $data + ); + + return; + } + + /* + * Key not seen before. Add to array. + */ + $this->keysSeen[$key] = [ + 'item' => $itemNr, + 'ptr' => $startPtr, + ]; + + if ($integerKey === true && $key > $this->currentMaxIntKey) { + $this->currentMaxIntKey = $key; + } + } + + /** + * Process an array item without an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the array item, + * which in this case will be the first token of the array + * value part of the array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return void + */ + public function processNoKey(File $phpcsFile, $startPtr, $itemNr) + { + ++$this->currentMaxIntKey; + $this->keysSeen[$this->currentMaxIntKey] = [ + 'item' => $itemNr, + 'ptr' => $startPtr, + ]; + } +} diff --git a/Universal/Sniffs/Arrays/MixedArrayKeyTypesSniff.php b/Universal/Sniffs/Arrays/MixedArrayKeyTypesSniff.php new file mode 100644 index 0000000..516472e --- /dev/null +++ b/Universal/Sniffs/Arrays/MixedArrayKeyTypesSniff.php @@ -0,0 +1,170 @@ +seenStringKey = false; + $this->seenNumericKey = false; + + parent::processArray($phpcsFile); + } + + /** + * Process the tokens in an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the "key" part of + * an array item. + * @param int $endPtr The stack pointer to the last token in the "key" part of + * an array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return void + */ + public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr) + { + $key = $this->getActualArrayKey($phpcsFile, $startPtr, $endPtr); + if (isset($key) === false) { + // Key could not be determined. + return; + } + + $integerKey = \is_int($key); + + // Handle integer key. + if ($integerKey === true) { + if ($this->seenStringKey === false) { + if ($this->seenNumericKey !== false) { + // Already seen a numeric key before. + return; + } + + $this->seenNumericKey = true; + return; + } + + // Ok, so we've seen a string key before and now see an explicit numeric key. + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); + $phpcsFile->addError( + 'Arrays should have either numeric keys or string keys. Explicit numeric key detected,' + . ' while all previous keys in this array were string keys.', + $firstNonEmpty, + 'ExplicitNumericKey' + ); + + // Stop the loop. + return true; + } + + // Handle string key. + if ($this->seenNumericKey === false) { + if ($this->seenStringKey !== false) { + // Already seen a string key before. + return; + } + + $this->seenStringKey = true; + return; + } + + // Ok, so we've seen a numeric key before and now see a string key. + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); + $phpcsFile->addError( + 'Arrays should have either numeric keys or string keys. String key detected,' + . ' while all previous keys in this array were integer based keys.', + $firstNonEmpty, + 'StringKey' + ); + + // Stop the loop. + return true; + } + + /** + * Process an array item without an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the array item, + * which in this case will be the first token of the array + * value part of the array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return void + */ + public function processNoKey(File $phpcsFile, $startPtr, $itemNr) + { + if ($this->seenStringKey === false) { + if ($this->seenNumericKey !== false) { + // Already seen a numeric key before. + return; + } + + $this->seenNumericKey = true; + return; + } + + // Ok, so we've seen a string key before and now see an implicit numeric key. + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); + $phpcsFile->addError( + 'Arrays should have either numeric keys or string keys. Implicit numeric key detected,' + . ' while all previous keys in this array were string keys.', + $firstNonEmpty, + 'ImplicitNumericKey' + ); + + // Stop the loop. + return true; + } +} diff --git a/Universal/Sniffs/Arrays/MixedKeyedUnkeyedArraySniff.php b/Universal/Sniffs/Arrays/MixedKeyedUnkeyedArraySniff.php new file mode 100644 index 0000000..ee7679d --- /dev/null +++ b/Universal/Sniffs/Arrays/MixedKeyedUnkeyedArraySniff.php @@ -0,0 +1,134 @@ + => + */ + private $itemsWithoutKey = []; + + /** + * Process the array declaration. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * + * @return void + */ + public function processArray(File $phpcsFile) + { + // Reset properties before processing this array. + $this->hasKeys = false; + $this->itemsWithoutKey = []; + + parent::processArray($phpcsFile); + } + + /** + * Process the tokens in an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the "key" part of + * an array item. + * @param int $endPtr The stack pointer to the last token in the "key" part of + * an array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return void + */ + public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr) + { + $this->hasKeys = true; + + // Process any previously encountered items without keys. + if (empty($this->itemsWithoutKey) === false) { + foreach ($this->itemsWithoutKey as $itemNr => $stackPtr) { + $phpcsFile->addError( + 'Inconsistent array detected. A mix of keyed and unkeyed array items is not allowed.' + . ' The array item in position %d does not have an array key.', + $stackPtr, + 'Found', + [$itemNr] + ); + } + + // No need to do this again. + $this->itemsWithoutKey = []; + } + } + + /** + * Process an array item without an array key. + * + * @since 1.0.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the + * token was found. + * @param int $startPtr The stack pointer to the first token in the array item, + * which in this case will be the first token of the array + * value part of the array item. + * @param int $itemNr Which item in the array is being handled. + * + * @return void + */ + public function processNoKey(File $phpcsFile, $startPtr, $itemNr) + { + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); + if ($firstNonEmpty === false) { + // Shouldn't be possible. + return; + } + + // If we already know there are keys in the array, throw an error message straight away. + if ($this->hasKeys === true) { + $phpcsFile->addError( + 'Inconsistent array detected. A mix of keyed and unkeyed array items is not allowed.' + . ' The array item in position %d does not have an array key.', + $firstNonEmpty, + 'Found', + [$itemNr] + ); + } else { + // Save the array item info for later in case we do encounter an array key later on in the array. + $this->itemsWithoutKey[$itemNr] = $firstNonEmpty; + } + } +} diff --git a/Universal/Sniffs/ControlStructures/IfElseDeclarationSniff.php b/Universal/Sniffs/ControlStructures/IfElseDeclarationSniff.php new file mode 100644 index 0000000..738428d --- /dev/null +++ b/Universal/Sniffs/ControlStructures/IfElseDeclarationSniff.php @@ -0,0 +1,150 @@ +getTokens(); + + /* + * Check for control structures without braces and alternative syntax. + */ + $scopePtr = $stackPtr; + if (isset($tokens[$stackPtr]['scope_opener']) === false) { + // Deal with "else if". + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($tokens[$next]['code'] === \T_IF) { + $scopePtr = $next; + } + } + + if (isset($tokens[$scopePtr]['scope_opener']) === false + || $tokens[$tokens[$scopePtr]['scope_opener']]['code'] === \T_COLON + ) { + // No scope opener found or alternative syntax (not our concern). + return; + } + + /* + * Check whether the else(if) is on a new line. + */ + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($prevNonEmpty === false || $tokens[$prevNonEmpty]['code'] !== \T_CLOSE_CURLY_BRACKET) { + // Parse error. Not our concern. + return; + } + + if ($tokens[$prevNonEmpty]['line'] !== $tokens[$stackPtr]['line']) { + $phpcsFile->recordMetric($stackPtr, 'Else(if) on a new line', 'yes'); + return; + } + + $phpcsFile->recordMetric($stackPtr, 'Else(if) on a new line', 'no'); + + $errorBase = \strtoupper($tokens[$stackPtr]['content']); + $error = $errorBase . ' statement must be on a new line.'; + + $prevNonWhitespace = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true); + + if ($prevNonWhitespace !== $prevNonEmpty) { + // Comment found between previous scope closer and the keyword. + $fix = $phpcsFile->addError($error, $stackPtr, 'NoNewLine'); + return; + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoNewLine'); + if ($fix === false) { + return; + } + + /* + * Fix it. + */ + + // Figure out the indentation for the else(if). + $indentBase = $prevNonEmpty; + if (isset($tokens[$prevNonEmpty]['scope_condition']) === true + && ($tokens[$tokens[$prevNonEmpty]['scope_condition']]['column'] === 1 + || ($tokens[($tokens[$prevNonEmpty]['scope_condition'] - 1)]['code'] === \T_WHITESPACE + && $tokens[($tokens[$prevNonEmpty]['scope_condition'] - 1)]['column'] === 1)) + ) { + // Base the indentation off the previous if/elseif if on a line by itself. + $indentBase = $tokens[$prevNonEmpty]['scope_condition']; + } + + $indent = ''; + $firstOnIndentLine = $indentBase; + if ($tokens[$firstOnIndentLine]['column'] !== 1) { + while (isset($tokens[($firstOnIndentLine - 1)]) && $tokens[--$firstOnIndentLine]['column'] !== 1); + + if ($tokens[$firstOnIndentLine]['code'] === \T_WHITESPACE) { + $indent = $tokens[$firstOnIndentLine]['content']; + } + } + + $phpcsFile->fixer->beginChangeset(); + + // Remove any whitespace between the previous scope closer and the else(if). + for ($i = ($prevNonEmpty + 1); $i < $stackPtr; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->addContent($prevNonEmpty, $phpcsFile->eolChar . $indent); + $phpcsFile->fixer->endChangeset(); + } +} diff --git a/Universal/Sniffs/Lists/DisallowLongListSyntaxSniff.php b/Universal/Sniffs/Lists/DisallowLongListSyntaxSniff.php new file mode 100644 index 0000000..1949926 --- /dev/null +++ b/Universal/Sniffs/Lists/DisallowLongListSyntaxSniff.php @@ -0,0 +1,73 @@ +recordMetric($stackPtr, 'Short list syntax used', 'no'); + + $fix = $phpcsFile->addFixableError('Long list syntax is not allowed', $stackPtr, 'Found'); + + if ($fix === true) { + $opener = $openClose['opener']; + $closer = $openClose['closer']; + + $phpcsFile->fixer->beginChangeset(); + + $phpcsFile->fixer->replaceToken($stackPtr, ''); + $phpcsFile->fixer->replaceToken($opener, '['); + $phpcsFile->fixer->replaceToken($closer, ']'); + + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/Universal/Sniffs/Lists/DisallowShortListSyntaxSniff.php b/Universal/Sniffs/Lists/DisallowShortListSyntaxSniff.php new file mode 100644 index 0000000..7a052e7 --- /dev/null +++ b/Universal/Sniffs/Lists/DisallowShortListSyntaxSniff.php @@ -0,0 +1,80 @@ +getTokens(); + $openClose = Lists::getOpenClose($phpcsFile, $stackPtr); + + if ($openClose === false) { + // Not a short list, live coding or parse error. + if (isset($tokens[$stackPtr]['bracket_closer']) === true) { + // No need to examine nested subs of this short array/array access. + return $tokens[$stackPtr]['bracket_closer']; + } + + return; + } + + $phpcsFile->recordMetric($stackPtr, 'Short list syntax used', 'yes'); + + $fix = $phpcsFile->addFixableError('Short list syntax is not allowed', $stackPtr, 'Found'); + + if ($fix === true) { + $opener = $openClose['opener']; + $closer = $openClose['closer']; + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($opener, 'list('); + $phpcsFile->fixer->replaceToken($closer, ')'); + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/Universal/Sniffs/Namespaces/DisallowCurlyBraceSyntaxSniff.php b/Universal/Sniffs/Namespaces/DisallowCurlyBraceSyntaxSniff.php new file mode 100644 index 0000000..b0eed5f --- /dev/null +++ b/Universal/Sniffs/Namespaces/DisallowCurlyBraceSyntaxSniff.php @@ -0,0 +1,72 @@ +getTokens(); + + if (isset($tokens[$stackPtr]['scope_condition']) === false + || $tokens[$stackPtr]['scope_condition'] !== $stackPtr + ) { + $phpcsFile->recordMetric($stackPtr, 'Namespace declaration using curly brace syntax', 'no'); + return; + } + + $phpcsFile->recordMetric($stackPtr, 'Namespace declaration using curly brace syntax', 'yes'); + + $phpcsFile->addError( + 'Namespace declarations using the curly brace syntax are not allowed.', + $stackPtr, + 'Forbidden' + ); + } +} diff --git a/Universal/Sniffs/Namespaces/EnforceCurlyBraceSyntaxSniff.php b/Universal/Sniffs/Namespaces/EnforceCurlyBraceSyntaxSniff.php new file mode 100644 index 0000000..a5a9825 --- /dev/null +++ b/Universal/Sniffs/Namespaces/EnforceCurlyBraceSyntaxSniff.php @@ -0,0 +1,72 @@ +getTokens(); + + if (isset($tokens[$stackPtr]['scope_condition']) === true + && $tokens[$stackPtr]['scope_condition'] === $stackPtr + ) { + $phpcsFile->recordMetric($stackPtr, 'Namespace declaration using curly brace syntax', 'yes'); + return; + } + + $phpcsFile->recordMetric($stackPtr, 'Namespace declaration using curly brace syntax', 'no'); + + $phpcsFile->addError( + 'Namespace declarations without curly braces are not allowed.', + $stackPtr, + 'Forbidden' + ); + } +} diff --git a/Universal/Sniffs/Namespaces/OneDeclarationPerFileSniff.php b/Universal/Sniffs/Namespaces/OneDeclarationPerFileSniff.php new file mode 100644 index 0000000..d18c59a --- /dev/null +++ b/Universal/Sniffs/Namespaces/OneDeclarationPerFileSniff.php @@ -0,0 +1,96 @@ +getFilename(); + if ($this->currentFile !== $fileName) { + // Reset the properties for each new file. + $this->currentFile = $fileName; + $this->declarationSeen = false; + } + + if (Namespaces::isDeclaration($phpcsFile, $stackPtr) === false) { + // Namespace operator, not a declaration; or live coding/parse error. + return; + } + + if ($this->declarationSeen === false) { + // This is the first namespace declaration in the file. + $this->declarationSeen = $stackPtr; + return; + } + + $tokens = $phpcsFile->getTokens(); + + // OK, so this is a file with multiple namespace declarations. + $phpcsFile->addError( + 'There should be only one namespace declaration per file. The first declaration was found on line %d', + $stackPtr, + 'MultipleFound', + [$tokens[$this->declarationSeen]['line']] + ); + } +} diff --git a/Universal/Sniffs/UseStatements/DisallowUseClassSniff.php b/Universal/Sniffs/UseStatements/DisallowUseClassSniff.php new file mode 100644 index 0000000..4ada653 --- /dev/null +++ b/Universal/Sniffs/UseStatements/DisallowUseClassSniff.php @@ -0,0 +1,107 @@ +getTokens(); + $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); + + foreach ($statements['name'] as $alias => $fullName) { + $reportPtr = $stackPtr; + do { + $reportPtr = $phpcsFile->findNext(\T_STRING, ($reportPtr + 1), $endOfStatement, false, $alias); + if ($reportPtr === false) { + // Shouldn't be possible. + continue 2; + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($reportPtr + 1), $endOfStatement, true); + if ($next !== false && $tokens[$next]['code'] === \T_NS_SEPARATOR) { + // Namespace level with same name. Continue searching + continue; + } + + break; + } while (true); + + $error = 'Use import statements for classes/traits/interfaces are not allowed.'; + $error .= ' Found import statement for: "%s"'; + $data = [$fullName, $alias]; + + $offsetFromEnd = (\strlen($alias) + 1); + if (\substr($fullName, -$offsetFromEnd) === '\\' . $alias) { + $phpcsFile->recordMetric($reportPtr, 'Use import statement for class/interface/trait', 'without alias'); + + $phpcsFile->addError($error, $reportPtr, 'FoundWithoutAlias', $data); + continue; + } + + $phpcsFile->recordMetric($reportPtr, 'Use import statement for class/interface/trait', 'with alias'); + + $error .= ' with alias: "%s"'; + $phpcsFile->addError($error, $reportPtr, 'FoundWithAlias', $data); + } + } +} diff --git a/Universal/Sniffs/UseStatements/DisallowUseConstSniff.php b/Universal/Sniffs/UseStatements/DisallowUseConstSniff.php new file mode 100644 index 0000000..77a4a32 --- /dev/null +++ b/Universal/Sniffs/UseStatements/DisallowUseConstSniff.php @@ -0,0 +1,107 @@ +getTokens(); + $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); + + foreach ($statements['const'] as $alias => $fullName) { + $reportPtr = $stackPtr; + do { + $reportPtr = $phpcsFile->findNext(\T_STRING, ($reportPtr + 1), $endOfStatement, false, $alias); + if ($reportPtr === false) { + // Shouldn't be possible. + continue 2; + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($reportPtr + 1), $endOfStatement, true); + if ($next !== false && $tokens[$next]['code'] === \T_NS_SEPARATOR) { + // Namespace level with same name. Continue searching + continue; + } + + break; + } while (true); + + $error = 'Use import statements for constants are not allowed.'; + $error .= ' Found import statement for: "%s"'; + $data = [$fullName, $alias]; + + $offsetFromEnd = (\strlen($alias) + 1); + if (\substr($fullName, -$offsetFromEnd) === '\\' . $alias) { + $phpcsFile->recordMetric($reportPtr, 'Use import statement for constant', 'without alias'); + + $phpcsFile->addError($error, $reportPtr, 'FoundWithoutAlias', $data); + continue; + } + + $phpcsFile->recordMetric($reportPtr, 'Use import statement for constant', 'with alias'); + + $error .= ' with alias: "%s"'; + $phpcsFile->addError($error, $reportPtr, 'FoundWithAlias', $data); + } + } +} diff --git a/Universal/Sniffs/UseStatements/DisallowUseFunctionSniff.php b/Universal/Sniffs/UseStatements/DisallowUseFunctionSniff.php new file mode 100644 index 0000000..0b5d7ea --- /dev/null +++ b/Universal/Sniffs/UseStatements/DisallowUseFunctionSniff.php @@ -0,0 +1,107 @@ +getTokens(); + $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); + + foreach ($statements['function'] as $alias => $fullName) { + $reportPtr = $stackPtr; + do { + $reportPtr = $phpcsFile->findNext(\T_STRING, ($reportPtr + 1), $endOfStatement, false, $alias); + if ($reportPtr === false) { + // Shouldn't be possible. + continue 2; + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($reportPtr + 1), $endOfStatement, true); + if ($next !== false && $tokens[$next]['code'] === \T_NS_SEPARATOR) { + // Namespace level with same name. Continue searching + continue; + } + + break; + } while (true); + + $error = 'Use import statements for functions are not allowed.'; + $error .= ' Found import statement for: "%s"'; + $data = [$fullName, $alias]; + + $offsetFromEnd = (\strlen($alias) + 1); + if (\substr($fullName, -$offsetFromEnd) === '\\' . $alias) { + $phpcsFile->recordMetric($reportPtr, 'Use import statement for functions', 'without alias'); + + $phpcsFile->addError($error, $reportPtr, 'FoundWithoutAlias', $data); + continue; + } + + $phpcsFile->recordMetric($reportPtr, 'Use import statement for functions', 'with alias'); + + $error .= ' with alias: "%s"'; + $phpcsFile->addError($error, $reportPtr, 'FoundWithAlias', $data); + } + } +} diff --git a/Universal/Tests/Arrays/DuplicateArrayKeyUnitTest.inc b/Universal/Tests/Arrays/DuplicateArrayKeyUnitTest.inc new file mode 100644 index 0000000..4871b64 --- /dev/null +++ b/Universal/Tests/Arrays/DuplicateArrayKeyUnitTest.inc @@ -0,0 +1,153 @@ + 'excluded', + MY_CONSTANT => 'excluded', + PHP_INT_MAX => 'excluded', + str_replace('.', '', '1.1') => 'excluded', + self::CONSTANT => 'excluded', + $obj->get_key() => 'excluded', + $obj->prop => 'excluded', + "my $var text" => 'excluded', + << 'excluded', + $var['key']{1} => 'excluded', +]; + +/* + * Let's find some duplicates. + */ + +$emptyStringKey = array( + '' => 'empty', + // All below will error. + null => 'null', + (string) false => 'false', +); + +$everythingZero = [ + '0', + // All below will error. + 0 => 'a', + 0.0 => 'b', + '0' => 'c', + 0b0 => 'd', + 0x0 => 'e', + 00 => 'f', + false => 'g', + 0.4 => 'h', + -0.8 => 'i', + 0e0 => 'j', + 0_0 => 'k', + -1 + 1 => 'l', + 3 * 0 => 'm', + 00.00 => 'n', + (int) 'nothing' => 'o', + 15 > 200 => 'p', + "0" => 'q', + 0. => 'r', + .0 => 's', + (true) ? 0 : 1 => 't', + ! true => 'u', +]; + +$everythingOne = [ + '0', + '1', + // All below will error. + 1 => 'a', + 1.1 => 'b', + '1' => 'c', + 0b1 => 'd', + 0x1 => 'e', + 01 => 'f', + true => 'g', + 1.2 => 'h', + 1e0 => 'i', + 0_1 => 'j', + -1 + 2 => 'k', + 3 * 0.5 => 'l', + 01.00 => 'm', + (int) '1 penny' => 'n', + 15 < 200 => 'o', + "1" => 'p', + 1. => 'q', + 001. => 'r', + (true) ? 1 : 0 => 's', + ! false => 't', + (string) true => 'u', +]; + +$everythingEleven = [ + 11 => 'a', + // All below will error. + 11.0 => 'b', + '11' => 'c', + 0b1011 => 'd', + 0Xb => 'e', + 013 => 'f', + 11.8 => 'g', + 1.1e1 => 'h', + 1_1 => 'i', + 0_13 => 'j', + -1 + 12 => 'k', + 22 / 2 => 'l', + 0011.0011 => 'm', + (int) '11 lane' => 'n', + "11" => 'o', + 11. => 'p', + 35 % 12 => 'q', +]; + +$textualStringKeyVariations = [ + 'abc' => 1, + 'def' => 2, + 'ghi' => 3, + // All below will error. + 'ab' . 'c' => 4, // Error. + << 5, // Error. + <<< 'NOW' +ghi +NOW + => 6, // Error. + "abc" => 7, // Error. +]; + +$testKeepingTrackOfHighestIntKey = array( + '' => 'empty', + 'a', // Int 0 + 'b', // Int 1 + 1 => 'c', // Int 1 - Error. + 5 => 'd', // Int 5 + 'e', // Int 6 + 6 => 'f', // Int 6 - Error. + false => 'g', // Int 0 - Error. + true => 'h', // Int 1 - Error. + 1.1 => 'i', // Int 1 - Error. + 6.5 => 'j', // Int 6 - Error. + 05 => 'k', // Int 5 - Error. + 0_6 => 'l', // Int 6 - Error. PHP 7.4 octal numeric literal. + 0x1 => 'm', // Int 1 - Error. + 0b0 => 'n', // Int 0 - Error. + null => 'o', // Empty string - Error. + 02.6e7 => 'p', // Int 26000000 + '96' => 'q', // Int 96 + 'r', // Int 26000001 + '26000001' => 's', // Int 26000001 - Error. + '96.3' => 't', // String '96.3' + 1 + 0 => 'u', // Int 1 - Error. + 1.1 - 0.5 => 'v', // Int 0 - Error. + -1 => 'w', // Int -1 + 'x', // Int 26000002 + '9' . '6' => 'y', // Int 96 - Error. + '1.' => 'z', // String '1.' +); diff --git a/Universal/Tests/Arrays/DuplicateArrayKeyUnitTest.php b/Universal/Tests/Arrays/DuplicateArrayKeyUnitTest.php new file mode 100644 index 0000000..e1e7335 --- /dev/null +++ b/Universal/Tests/Arrays/DuplicateArrayKeyUnitTest.php @@ -0,0 +1,124 @@ + => + */ + public function getErrorList() + { + return [ + 30 => 1, + 31 => 1, + 37 => 1, + 38 => 1, + 39 => 1, + 40 => 1, + 41 => 1, + 42 => 1, + 43 => 1, + 44 => 1, + 45 => 1, + 46 => 1, + 47 => 1, + 48 => 1, + 49 => 1, + 50 => 1, + 51 => 1, + 52 => 1, + 53 => 1, + 54 => 1, + 55 => 1, + 56 => 1, + 57 => 1, + 64 => 1, + 65 => 1, + 66 => 1, + 67 => 1, + 68 => 1, + 69 => 1, + 70 => 1, + 71 => 1, + 72 => 1, + 73 => 1, + 74 => 1, + 75 => 1, + 76 => 1, + 77 => 1, + 78 => 1, + 79 => 1, + 80 => 1, + 81 => 1, + 82 => 1, + 83 => 1, + 84 => 1, + 90 => 1, + 91 => 1, + 92 => 1, + 93 => 1, + 94 => 1, + 95 => 1, + 96 => 1, + 97 => 1, + 98 => 1, + 99 => 1, + 100 => 1, + 101 => 1, + 102 => 1, + 103 => 1, + 104 => 1, + 105 => 1, + 113 => 1, + 114 => 1, + 118 => 1, + 122 => 1, + 129 => 1, + 132 => 1, + 133 => 1, + 134 => 1, + 135 => 1, + 136 => 1, + 137 => 1, + 138 => 1, + 139 => 1, + 140 => 1, + 141 => 1, + 145 => 1, + 147 => 1, + 148 => 1, + 151 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/Arrays/MixedArrayKeyTypesUnitTest.inc b/Universal/Tests/Arrays/MixedArrayKeyTypesUnitTest.inc new file mode 100644 index 0000000..df364c1 --- /dev/null +++ b/Universal/Tests/Arrays/MixedArrayKeyTypesUnitTest.inc @@ -0,0 +1,49 @@ + 'a', + 2 => 'b', + 3 => 'c', + 4 => 'd', +); + +// OK: All items have numeric keys. +$array = array( + 'a', + 2 => 'b', + '3' => 'c', + 4 => 'd', +); + +// OK: All items have string keys. +$array = array( + 'a' => 'a', + 'b' => 'b', + 'c' => 'c', + 'd' => 'd', +); + +// Mixed numeric first. +$array = [ + 12 => 'numeric key', + 'value', + 'string' => 'string key', // Error. +]; + +// Mixed string first. +$array = [ + 'stringA' => 'string key', + 'stringB' => 'string key', + 12 => 'numeric key', // Error. +]; + +// Mixed string first, implicit numeric. +$array = [ + 'stringA' => 'string key', + 'numeric key', // Error. + 'stringB' => 'string key', +]; diff --git a/Universal/Tests/Arrays/MixedArrayKeyTypesUnitTest.php b/Universal/Tests/Arrays/MixedArrayKeyTypesUnitTest.php new file mode 100644 index 0000000..ea599da --- /dev/null +++ b/Universal/Tests/Arrays/MixedArrayKeyTypesUnitTest.php @@ -0,0 +1,48 @@ + => + */ + public function getErrorList() + { + return [ + 34 => 1, + 41 => 1, + 47 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/Arrays/MixedKeyedUnkeyedArrayUnitTest.inc b/Universal/Tests/Arrays/MixedKeyedUnkeyedArrayUnitTest.inc new file mode 100644 index 0000000..513d606 --- /dev/null +++ b/Universal/Tests/Arrays/MixedKeyedUnkeyedArrayUnitTest.inc @@ -0,0 +1,20 @@ + 'a', + 2 => 'b', + 3 => 'c', + 4 => 'd', +); + +// Mixed. +$array = [ + 'value', + 12 => 'numeric key', + 'string' => 'string key', + 'value', +]; diff --git a/Universal/Tests/Arrays/MixedKeyedUnkeyedArrayUnitTest.php b/Universal/Tests/Arrays/MixedKeyedUnkeyedArrayUnitTest.php new file mode 100644 index 0000000..3d66ac4 --- /dev/null +++ b/Universal/Tests/Arrays/MixedKeyedUnkeyedArrayUnitTest.php @@ -0,0 +1,47 @@ + => + */ + public function getErrorList() + { + return [ + 16 => 1, + 19 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/ControlStructures/IfElseDeclarationUnitTest.inc b/Universal/Tests/ControlStructures/IfElseDeclarationUnitTest.inc new file mode 100644 index 0000000..78fc04b --- /dev/null +++ b/Universal/Tests/ControlStructures/IfElseDeclarationUnitTest.inc @@ -0,0 +1,138 @@ + => + */ + public function getErrorList() + { + return [ + 79 => 1, + 85 => 1, + 87 => 1, + 91 => 1, + 94 => 1, + 96 => 1, + 107 => 1, + 113 => 1, + 115 => 1, + 119 => 1, + 126 => 1, + 131 => 2, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/Lists/DisallowLongListSyntaxUnitTest.inc b/Universal/Tests/Lists/DisallowLongListSyntaxUnitTest.inc new file mode 100644 index 0000000..9c450ca --- /dev/null +++ b/Universal/Tests/Lists/DisallowLongListSyntaxUnitTest.inc @@ -0,0 +1,21 @@ + + */ + public function getErrorList() + { + return [ + 2 => 1, + 4 => 2, + 6 => 2, + 7 => 1, + 9 => 1, + 16 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/Lists/DisallowShortListSyntaxUnitTest.inc b/Universal/Tests/Lists/DisallowShortListSyntaxUnitTest.inc new file mode 100644 index 0000000..f10974b --- /dev/null +++ b/Universal/Tests/Lists/DisallowShortListSyntaxUnitTest.inc @@ -0,0 +1,20 @@ + + */ + public function getErrorList() + { + return [ + 9 => 1, + 11 => 2, + 12 => 2, + 13 => 1, + 15 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/Namespaces/DisallowCurlyBraceSyntaxUnitTest.inc b/Universal/Tests/Namespaces/DisallowCurlyBraceSyntaxUnitTest.inc new file mode 100644 index 0000000..5505b88 --- /dev/null +++ b/Universal/Tests/Namespaces/DisallowCurlyBraceSyntaxUnitTest.inc @@ -0,0 +1,17 @@ + => + */ + public function getErrorList() + { + return [ + 7 => 1, + 9 => 1, + 14 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/Namespaces/EnforceCurlyBraceSyntaxUnitTest.inc b/Universal/Tests/Namespaces/EnforceCurlyBraceSyntaxUnitTest.inc new file mode 100644 index 0000000..cfadb1e --- /dev/null +++ b/Universal/Tests/Namespaces/EnforceCurlyBraceSyntaxUnitTest.inc @@ -0,0 +1,15 @@ + => + */ + public function getErrorList() + { + return [ + 10 => 1, + 12 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/Namespaces/OneDeclarationPerFileUnitTest.1.inc b/Universal/Tests/Namespaces/OneDeclarationPerFileUnitTest.1.inc new file mode 100644 index 0000000..9db88f0 --- /dev/null +++ b/Universal/Tests/Namespaces/OneDeclarationPerFileUnitTest.1.inc @@ -0,0 +1,22 @@ + => + */ + public function getErrorList($testFile = '') + { + switch ($testFile) { + case 'OneDeclarationPerFileUnitTest.1.inc': + return [ + 9 => 1, + 13 => 1, + 17 => 1, + ]; + + case 'OneDeclarationPerFileUnitTest.2.inc': + return [ + 10 => 1, + 15 => 1, + 20 => 1, + 26 => 1, + ]; + + default: + return []; + } + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/UseStatements/DisallowUseClassUnitTest.inc b/Universal/Tests/UseStatements/DisallowUseClassUnitTest.inc new file mode 100644 index 0000000..bb94e3b --- /dev/null +++ b/Universal/Tests/UseStatements/DisallowUseClassUnitTest.inc @@ -0,0 +1,38 @@ + => + */ + public function getErrorList() + { + return [ + 8 => 1, + 9 => 1, + 11 => 1, + 12 => 1, + 13 => 1, + 14 => 1, + 17 => 1, + 18 => 1, + 19 => 1, + 24 => 1, + 28 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/UseStatements/DisallowUseConstUnitTest.inc b/Universal/Tests/UseStatements/DisallowUseConstUnitTest.inc new file mode 100644 index 0000000..f03f5b5 --- /dev/null +++ b/Universal/Tests/UseStatements/DisallowUseConstUnitTest.inc @@ -0,0 +1,35 @@ + => + */ + public function getErrorList() + { + return [ + 8 => 1, + 9 => 1, + 11 => 1, + 12 => 1, + 15 => 1, + 16 => 1, + 23 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/UseStatements/DisallowUseFunctionUnitTest.inc b/Universal/Tests/UseStatements/DisallowUseFunctionUnitTest.inc new file mode 100644 index 0000000..9518677 --- /dev/null +++ b/Universal/Tests/UseStatements/DisallowUseFunctionUnitTest.inc @@ -0,0 +1,37 @@ + => + */ + public function getErrorList() + { + return [ + 8 => 1, + 9 => 1, + 11 => 1, + 12 => 1, + 13 => 1, + 16 => 1, + 17 => 1, + 18 => 1, + 24 => 1, + 26 => 1, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/ruleset.xml b/Universal/ruleset.xml new file mode 100644 index 0000000..33067f2 --- /dev/null +++ b/Universal/ruleset.xml @@ -0,0 +1,5 @@ + + + + A collection of universal sniffs. This standard is not designed to be used to check code. Include individual sniffs from this standard in a custom ruleset instead. + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..74595fe --- /dev/null +++ b/composer.json @@ -0,0 +1,57 @@ +{ + "name" : "phpcsstandards/phpcsextra", + "description" : "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "type" : "phpcodesniffer-standard", + "keywords" : [ "phpcs", "phpcbf", "standards", "php_codesniffer", "phpcodesniffer-standard" ], + "license" : "LGPL-3.0-or-later", + "authors" : [ + { + "name" : "Juliette Reinders Folmer", + "role" : "lead", + "homepage" : "https://github.com/jrfnl" + }, + { + "name" : "Contributors", + "homepage" : "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "support" : { + "issues" : "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "source" : "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "require" : { + "php" : ">=5.4", + "squizlabs/php_codesniffer" : "^3.3.1", + "dealerdirect/phpcodesniffer-composer-installer" : "^0.3 || ^0.4.1 || ^0.5 || ^0.6", + "phpcsstandards/phpcsutils" : "^1.0 || dev-develop" + }, + "require-dev" : { + "jakub-onderka/php-parallel-lint": "^1.0", + "jakub-onderka/php-console-highlighter": "^0.4", + "phpcsstandards/phpcsdevtools": "^1.0 || dev-develop", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts" : { + "install-standards": [ + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run" + ], + "lint": [ + "@php ./vendor/jakub-onderka/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git" + ], + "checkcs": [ + "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs" + ], + "fixcs": [ + "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" + ], + "check-complete": [ + "@php ./vendor/phpcsstandards/phpcsdevtools/bin/phpcs-check-feature-completeness ./NormalizedArrays ./Universal" + ], + "test": [ + "@php ./vendor/phpunit/phpunit/phpunit --filter NormalizedArrays ./vendor/squizlabs/php_codesniffer/tests/AllTests.php", + "@php ./vendor/phpunit/phpunit/phpunit --filter Universal ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" + ] + } +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..3a5f19a --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,78 @@ + + + Check the code of the PHPCSExtra package itself. + + + + . + + + */vendor/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /phpunit-bootstrap\.php$ + + + + + + + + /Universal/Sniffs/ControlStructures/IfElseDeclarationSniff\.php$ + + + diff --git a/phpunit-bootstrap.php b/phpunit-bootstrap.php new file mode 100644 index 0000000..c76945e --- /dev/null +++ b/phpunit-bootstrap.php @@ -0,0 +1,82 @@ + true, + 'Universal' => true, +]; + +$allStandards = PHP_CodeSniffer\Util\Standards::getInstalledStandards(); +$allStandards[] = 'Generic'; + +$standardsToIgnore = []; +foreach ($allStandards as $standard) { + if (isset($phpcsExtraStandards[$standard]) === true) { + continue; + } + + $standardsToIgnore[] = $standard; +} + +$standardsToIgnoreString = \implode(',', $standardsToIgnore); +\putenv("PHPCS_IGNORE_TESTS={$standardsToIgnoreString}"); + +// Clean up. +unset($ds, $phpcsDir, $composerPHPCSPath, $allStandards, $standardsToIgnore, $standard, $standardsToIgnoreString); diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..fbfbd16 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,18 @@ + + + + + + ./NormalizedArrays/Tests/ + ./Universal/Tests/ + + + +