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/
+
+
+
+