diff --git a/.github/SECURITY.md b/.github/SECURITY.md
deleted file mode 100644
index a4360b1e..00000000
--- a/.github/SECURITY.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Security Policy
-
-## Supported Versions
-
-The latest patch version of the `1.x` release series is supported for security updates.
-
-## Reporting a Vulnerability
-
-PHPCSExtra is a developer tool and should generally not be used in a production (web accessible) environment.
-
-Having said that, responsible disclosure of security issues is highly appreciated.
-
-**Please do not report or discuss security vulnerabilities through public GitHub issues, discussions, or pull requests.**
-
-Issues can be reported privately to the maintainers by opening a [Security vulnerability report](https://github.com/PHPCSStandards/PHPCSExtra/security/advisories/new).
-
-### Preferences
-
-* Please provide detailed reports with reproducible steps and a clearly defined impact.
-* Include the version number of the vulnerable package in your report.
-* Fixes are most welcome.
- A private PR can be created from the security report to work on and discuss the patch.
diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml
index 3ae47d5b..86acc3a9 100644
--- a/.github/workflows/basics.yml
+++ b/.github/workflows/basics.yml
@@ -48,7 +48,7 @@ jobs:
composer require --no-update squizlabs/php_codesniffer:"dev-master" --no-interaction
# Install dependencies and handle caching in one go.
- # @link https://github.com/marketplace/actions/install-composer-dependencies
+ # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
- name: Install Composer dependencies
uses: "ramsey/composer-install@v2"
with:
@@ -62,7 +62,8 @@ jobs:
# Show XML violations inline in the file diff.
# @link https://github.com/marketplace/actions/xmllint-problem-matcher
- - uses: korelstar/xmllint-problem-matcher@v1
+ - name: Enable showing XML issues inline
+ uses: korelstar/xmllint-problem-matcher@v1
# Validate the Ruleset XML file.
# @link http://xmlsoft.org/xmllint.html
@@ -112,7 +113,7 @@ jobs:
# Install dependencies and handle caching in one go.
# Dependencies need to be installed to make sure the PHPCS and PHPUnit classes are recognized.
- # @link https://github.com/marketplace/actions/install-composer-dependencies
+ # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
- name: Install Composer dependencies
uses: "ramsey/composer-install@v2"
with:
@@ -131,7 +132,7 @@ jobs:
uses: actions/checkout@v4
- name: Set up node and enable caching of dependencies
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: '16'
diff --git a/.github/workflows/label-remove-outdated.yml b/.github/workflows/label-remove-outdated.yml
new file mode 100644
index 00000000..ce4bc4ce
--- /dev/null
+++ b/.github/workflows/label-remove-outdated.yml
@@ -0,0 +1,53 @@
+name: Remove outdated labels
+
+on:
+ # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
+ issues:
+ types:
+ - closed
+ pull_request_target:
+ types:
+ - closed
+
+jobs:
+ on-issue-close:
+ runs-on: ubuntu-latest
+ if: github.repository_owner == 'PHPCSStandards' && github.event.issue.state == 'closed'
+
+ name: Clean up labels on issue close
+
+ steps:
+ - uses: mondeja/remove-labels-gh-action@v1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ labels: |
+ Status: awaiting feedback
+ Status: wait for upstream
+
+ on-pr-merge:
+ runs-on: ubuntu-latest
+ if: github.repository_owner == 'PHPCSStandards' && github.event.pull_request.merged == true
+
+ name: Clean up labels on PR merge
+
+ steps:
+ - uses: mondeja/remove-labels-gh-action@v1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ labels: |
+ Status: awaiting feedback
+ Status: wait for upstream
+
+ on-pr-close:
+ runs-on: ubuntu-latest
+ if: github.repository_owner == 'PHPCSStandards' && github.event_name == 'pull_request_target' && github.event.pull_request.merged == false
+
+ name: Clean up labels on PR close
+
+ steps:
+ - uses: mondeja/remove-labels-gh-action@v1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ labels: |
+ Status: awaiting feedback
+ Status: wait for upstream
diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml
index 2b90659f..1711cd4e 100644
--- a/.github/workflows/quicktest.yml
+++ b/.github/workflows/quicktest.yml
@@ -60,7 +60,7 @@ jobs:
run: composer require squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" --no-update --no-scripts --no-interaction
# Install dependencies and handle caching in one go.
- # @link https://github.com/marketplace/actions/install-composer-dependencies
+ # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
- name: Install Composer dependencies - normal
if: matrix.php != 'latest'
uses: "ramsey/composer-install@v2"
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 3b203cd3..2ed8aaf2 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -32,12 +32,12 @@ jobs:
# @link https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix
#
# The matrix is set up so as not to duplicate the builds which are run for code coverage.
- php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '8.0', '8.1', '8.2', '8.3']
+ php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '8.0', '8.1', '8.2', '8.3', '8.4']
phpcs_version: ['lowest', 'dev-master']
name: "Test${{ matrix.phpcs_version == 'dev-master' && ' + Lint' || '' }}: PHP ${{ matrix.php }} - PHPCS ${{ matrix.phpcs_version }}"
- continue-on-error: ${{ matrix.php == '8.3' }}
+ continue-on-error: ${{ matrix.php == '8.4' }}
steps:
- name: Checkout code
@@ -66,7 +66,7 @@ jobs:
run: composer require squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" --no-update --no-scripts --no-interaction
# Install dependencies and handle caching in one go.
- # @link https://github.com/marketplace/actions/install-composer-dependencies
+ # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
- name: Install Composer dependencies - normal
if: ${{ startsWith( matrix.php, '8' ) == false }}
uses: "ramsey/composer-install@v2"
@@ -148,7 +148,7 @@ jobs:
run: composer require squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" --no-update --no-scripts --no-interaction
# Install dependencies and handle caching in one go.
- # @link https://github.com/marketplace/actions/install-composer-dependencies
+ # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
- name: Install Composer dependencies
uses: "ramsey/composer-install@v2"
with:
@@ -168,37 +168,14 @@ jobs:
- name: Run the unit tests with code coverage
run: composer coverage
- # PHP Coveralls v2 (which supports GH Actions) has a PHP 5.5 minimum, so switch the PHP version.
- - name: Switch to PHP latest
- if: ${{ success() && matrix.php == '5.4' }}
- uses: shivammathur/setup-php@v2
- with:
- php-version: 'latest'
- coverage: none
-
- # Global install is used to prevent a conflict with the local composer.lock.
- - name: Install Coveralls
+ - name: Upload coverage results to Coveralls
if: ${{ success() }}
- run: composer global require php-coveralls/php-coveralls:"^2.6.0" --no-interaction
-
- - name: Upload coverage results to Coveralls (normal)
- if: ${{ success() && github.actor != 'dependabot[bot]' }}
- env:
- COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
- COVERALLS_PARALLEL: true
- COVERALLS_FLAG_NAME: php-${{ matrix.php }}-phpcs-${{ matrix.phpcs_version }}
- run: php-coveralls -v -x build/logs/clover.xml
-
- # Dependabot does not have access to secrets, other than the GH token.
- # Ref: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions
- # Ref: https://github.com/lemurheavy/coveralls-public/issues/1721
- - name: Upload coverage results to Coveralls (Dependabot)
- if: ${{ success() && github.actor == 'dependabot[bot]' }}
- env:
- COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- COVERALLS_PARALLEL: true
- COVERALLS_FLAG_NAME: php-${{ matrix.php }}-phpcs-${{ matrix.phpcs_version }}
- run: php-coveralls -v -x build/logs/clover.xml
+ uses: coverallsapp/github-action@v2
+ with:
+ format: clover
+ file: build/logs/clover.xml
+ flag-name: php-${{ matrix.php }}-phpcs-${{ matrix.phpcs_version }}
+ parallel: true
coveralls-finish:
needs: coverage
@@ -207,19 +184,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - name: Coveralls Finished (normal)
- if: ${{ github.actor != 'dependabot[bot]' }}
- uses: coverallsapp/github-action@v2
- with:
- github-token: ${{ secrets.COVERALLS_TOKEN }}
- parallel-finished: true
-
- # Dependabot does not have access to secrets, other than the GH token.
- # Ref: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions
- # Ref: https://github.com/lemurheavy/coveralls-public/issues/1721
- - name: Coveralls Finished (Dependabot)
- if: ${{ github.actor == 'dependabot[bot]' }}
+ - name: Coveralls Finished
uses: coverallsapp/github-action@v2
with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
diff --git a/.yamllint.yml b/.yamllint.yml
index 8dc953ff..b5c4202d 100644
--- a/.yamllint.yml
+++ b/.yamllint.yml
@@ -6,6 +6,7 @@ yaml-files:
- '*.yaml'
- '*.yml'
- '.yamllint'
+ - 'phpstan.neon*'
# Rule documentation: https://yamllint.readthedocs.io/en/stable/rules.html
rules:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d2fffeb6..5a619724 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,36 @@ This projects adheres to [Keep a CHANGELOG](http://keepachangelog.com/) and uses
_Nothing yet._
+## [1.2.0] - 2023-12-02
+
+### Added
+
+#### Universal
+
+* :wrench: :books: New `Universal.CodeAnalysis.NoDoubleNegative` sniff to detect double negatives (!!) and advise to use a boolean cast instead. Thanks [@diedexx] for reviewing. [#277]
+* :wrench: :books: New `Universal.Operators.ConcatPosition` sniff to enforce that the concatenation operator for multi-line concatenations is in a preferred position, either always at the start of the next line or always at the end of the previous line. [#294]
+* :wrench: :bar_chart: :books: New `Universal.PHP.LowercasePHPTag` sniff to enforce that the "PHP" in a PHP open tag is lowercase. Thanks [@fredden] for reviewing. [#276]
+
+### Changed
+
+#### NormalizedArrays
+
+* `NormalizedArrays.Arrays.CommaAfterLast`: the sniff now has two extra error codes to distinguish between multi-line arrays with the last array item on the _same line_ as the array closer vs the last array item being on a line _before_ the array closer. Thanks [@stronk7] for suggesting and patching this. [#283], [#284]
+ These new error codes allow for selectively excluding that specific situation from triggering the sniff.
+ The new error codes are `FoundMultiLineCloserSameLine` (for `multiLine="forbid"`) and `MissingMultiLineCloserSameLine` (for `multiLine="enforce"`).
+ The pre-existing `FoundMultiLine` and `FoundSingleLine` error codes continue to be used for multi-line arrays with the last array item on a different line than the array closer.
+
+#### Other
+
+* Various housekeeping.
+
+[#276]: https://github.com/PHPCSStandards/PHPCSExtra/pull/276
+[#277]: https://github.com/PHPCSStandards/PHPCSExtra/pull/277
+[#283]: https://github.com/PHPCSStandards/PHPCSExtra/issues/283
+[#284]: https://github.com/PHPCSStandards/PHPCSExtra/pull/284
+[#294]: https://github.com/PHPCSStandards/PHPCSExtra/pull/294
+
+
## [1.1.2] - 2023-09-21
### Changed
@@ -523,6 +553,7 @@ This initial alpha release contains the following sniffs:
[php_version-config]: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Configuration-Options#setting-the-php-version
[Unreleased]: https://github.com/PHPCSStandards/PHPCSExtra/compare/stable...HEAD
+[1.2.0]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.1.2...1.2.0
[1.1.2]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.1.1...1.1.2
[1.1.1]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.1.0...1.1.1
[1.1.0]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.4...1.1.0
@@ -537,5 +568,8 @@ This initial alpha release contains the following sniffs:
[@anomiex]: https://github.com/anomiex
[@derickr]: https://github.com/derickr
+[@diedexx]: https://github.com/diedexx
+[@fredden]: https://github.com/fredden
[@GaryJones]: https://github.com/GaryJones
+[@stronk7]: https://github.com/stronk7
[@szepeviktor]: https://github.com/szepeviktor
diff --git a/NormalizedArrays/Sniffs/Arrays/CommaAfterLastSniff.php b/NormalizedArrays/Sniffs/Arrays/CommaAfterLastSniff.php
index d03d1ffc..7384421d 100644
--- a/NormalizedArrays/Sniffs/Arrays/CommaAfterLastSniff.php
+++ b/NormalizedArrays/Sniffs/Arrays/CommaAfterLastSniff.php
@@ -145,6 +145,13 @@ public function process(File $phpcsFile, $stackPtr)
return;
}
+ // If the closer is on the same line as the last element, change the error code for multi-line arrays.
+ if ($errorCode === 'MultiLine'
+ && $tokens[$lastNonEmpty]['line'] === $tokens[$closer]['line']
+ ) {
+ $errorCode .= 'CloserSameLine';
+ }
+
$isComma = ($tokens[$lastNonEmpty]['code'] === \T_COMMA);
$phpcsFile->recordMetric(
diff --git a/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.1.inc b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.1.inc
index 8615ee9e..46abe48b 100644
--- a/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.1.inc
+++ b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.1.inc
@@ -166,6 +166,56 @@ EOD
, /*comment*/
) );
+/**
+ * Tests enforcing a comma after the last array item when the closer is in the same line. See #283.
+ */
+// phpcs:set NormalizedArrays.Arrays.CommaAfterLast multiLine enforce
+
+$missing = array(
+ 1, 2,
+ 3, 4);
+
+$missing = [
+ '1', '2',
+ '3', '4'];
+
+$missing_but_good = [
+ '1', '2',
+ '3', '4']; // phpcs:ignore NormalizedArrays.Arrays.CommaAfterLast.MissingMultiLineCloserSameLine
+
+$good = array(
+ 1, 2,
+ 3, 4,);
+
+$good = [
+ '1', '2',
+ '3', '4',];
+
+/**
+ * Tests forbidding a comma after the last array item when the closer is in the same line. See #283.
+ */
+// phpcs:set NormalizedArrays.Arrays.CommaAfterLast multiLine forbid
+
+$found = array(
+ 1, 2,
+ 3, 4,);
+
+$found = [
+ '1', '2',
+ '3', '4',];
+
+$found_but_good = [
+ '1', '2',
+ '3', '4',]; // phpcs:ignore NormalizedArrays.Arrays.CommaAfterLast.FoundMultiLineCloserSameLine
+
+$good = array(
+ 1, 2,
+ 3, 4);
+
+$good = [
+ '1', '2',
+ '3', '4'];
+
// Reset the properties to the defaults.
// phpcs:set NormalizedArrays.Arrays.CommaAfterLast singleLine forbid
// phpcs:set NormalizedArrays.Arrays.CommaAfterLast multiLine enforce
diff --git a/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.1.inc.fixed b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.1.inc.fixed
index d06573c1..09ab321d 100644
--- a/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.1.inc.fixed
+++ b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.1.inc.fixed
@@ -166,6 +166,56 @@ EOD
/*comment*/
) );
+/**
+ * Tests enforcing a comma after the last array item when the closer is in the same line. See #283.
+ */
+// phpcs:set NormalizedArrays.Arrays.CommaAfterLast multiLine enforce
+
+$missing = array(
+ 1, 2,
+ 3, 4,);
+
+$missing = [
+ '1', '2',
+ '3', '4',];
+
+$missing_but_good = [
+ '1', '2',
+ '3', '4']; // phpcs:ignore NormalizedArrays.Arrays.CommaAfterLast.MissingMultiLineCloserSameLine
+
+$good = array(
+ 1, 2,
+ 3, 4,);
+
+$good = [
+ '1', '2',
+ '3', '4',];
+
+/**
+ * Tests forbidding a comma after the last array item when the closer is in the same line. See #283.
+ */
+// phpcs:set NormalizedArrays.Arrays.CommaAfterLast multiLine forbid
+
+$found = array(
+ 1, 2,
+ 3, 4);
+
+$found = [
+ '1', '2',
+ '3', '4'];
+
+$found_but_good = [
+ '1', '2',
+ '3', '4',]; // phpcs:ignore NormalizedArrays.Arrays.CommaAfterLast.FoundMultiLineCloserSameLine
+
+$good = array(
+ 1, 2,
+ 3, 4);
+
+$good = [
+ '1', '2',
+ '3', '4'];
+
// Reset the properties to the defaults.
// phpcs:set NormalizedArrays.Arrays.CommaAfterLast singleLine forbid
// phpcs:set NormalizedArrays.Arrays.CommaAfterLast multiLine enforce
diff --git a/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.php b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.php
index fbffdc12..f4463550 100644
--- a/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.php
+++ b/NormalizedArrays/Tests/Arrays/CommaAfterLastUnitTest.php
@@ -52,6 +52,10 @@ public function getErrorList($testFile = '')
152 => 1,
159 => 1,
166 => 1,
+ 176 => 1,
+ 180 => 1,
+ 201 => 1,
+ 205 => 1,
];
case 'CommaAfterLastUnitTest.2.inc':
diff --git a/README.md b/README.md
index 22ba5382..9e70026a 100644
--- a/README.md
+++ b/README.md
@@ -9,12 +9,13 @@ PHPCSExtra
[![Latest Unstable Version](https://img.shields.io/badge/unstable-dev--develop-e68718.svg?maxAge=2419200)](https://packagist.org/packages/phpcsstandards/phpcsextra#dev-develop)
[![Last Commit to Unstable](https://img.shields.io/github/last-commit/PHPCSStandards/PHPCSExtra/develop.svg)](https://github.com/PHPCSStandards/PHPCSExtra/commits/develop)
-[![Minimum PHP Version](https://img.shields.io/packagist/php-v/phpcsstandards/phpcsextra.svg?maxAge=3600)][phpcsextra-packagist]
[![CS Build Status](https://github.com/PHPCSStandards/PHPCSExtra/actions/workflows/basics.yml/badge.svg?branch=develop)][gha-qa-results]
[![Test Build Status](https://github.com/PHPCSStandards/PHPCSExtra/actions/workflows/test.yml/badge.svg?branch=develop)][gha-test-results]
-[![Tested on PHP 5.4 to 8.2](https://img.shields.io/badge/tested%20on-PHP%205.4%20|%205.5%20|%205.6%20|%207.0%20|%207.1%20|%207.2%20|%207.3%20|%207.4%20|%208.0%20|%208.1%20|%208.2-brightgreen.svg?maxAge=2419200)][gha-test-results]
[![Coverage Status](https://coveralls.io/repos/github/PHPCSStandards/PHPCSExtra/badge.svg)](https://coveralls.io/github/PHPCSStandards/PHPCSExtra)
+[![Minimum PHP Version](https://img.shields.io/packagist/php-v/phpcsstandards/phpcsextra.svg?maxAge=3600)][phpcsextra-packagist]
+[![Tested on PHP 5.4 to 8.3](https://img.shields.io/badge/tested%20on-PHP%205.4%20|%205.5%20|%205.6%20|%207.0%20|%207.1%20|%207.2%20|%207.3%20|%207.4%20|%208.0%20|%208.1%20|%208.2%20|%208.3-brightgreen.svg?maxAge=2419200)][gha-test-results]
+
[![License: LGPLv3](https://poser.pugx.org/phpcsstandards/phpcsextra/license)](https://github.com/PHPCSStandards/PHPCSExtra/blob/stable/LICENSE)
![Awesome](https://img.shields.io/badge/awesome%3F-yes!-brightgreen.svg)
@@ -61,7 +62,7 @@ Installing via Composer is highly recommended.
Run the following from the root of your project:
```bash
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
-composer require --dev phpcsstandards/phpcsextra:"^1.1.0"
+composer require --dev phpcsstandards/phpcsextra:"^1.2.0"
```
### Composer Global Installation
@@ -69,7 +70,7 @@ composer require --dev phpcsstandards/phpcsextra:"^1.1.0"
Alternatively, you may want to install this standard globally:
```bash
composer global config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
-composer global require --dev phpcsstandards/phpcsextra:"^1.1.0"
+composer global require --dev phpcsstandards/phpcsextra:"^1.2.0"
```
### Updating to a newer version
@@ -229,6 +230,13 @@ Detects `foreach` control structures which use the same variable for both the ke
Note: The fixer will maintain the existing behaviour of the code. This may not be the _intended_ behaviour.
+#### `Universal.CodeAnalysis.NoDoubleNegative` :wrench: :books:
+
+Detects double negation `!!` in code, which is effectively the same as a boolean cast, but with a much higher cognitive load.
+Also detects triple negation `!!!`, which is effectively the same as a single negation.
+
+The sniff has modular error codes to allow for disabling individual checks. The error codes are: `FoundDouble`, `FoundDoubleWithInstanceof` (not auto-fixable) and `FoundTriple`.
+
#### `Universal.CodeAnalysis.NoEchoSprintf` :wrench: :books:
Detects use of the inefficient `echo [v]sprintf(...);` combi. Use `[v]printf()` instead.
@@ -360,6 +368,14 @@ Enforce that the names used in a class/enum "implements" statement or an interfa
The fixer will separate each name with a comma and one space.
If alternative formatting is desired, a sniff which will check and fix the formatting should be added to the ruleset.
+#### `Universal.Operators.ConcatPosition` :wrench: :bar_chart: :books:
+
+Enforce that the concatenation operator for multi-line concatenations is in a preferred position, either always at the start of the next line or always at the end of the previous line.
+
+* This sniff contains an `allowOnly` property to set the preferred position for the operator.
+ Accepted values: (string) `"start"` or `"end"`. Defaults to `"start"`.
+* Note: mid-line concatenation is still allowed and will not be flagged by this sniff.
+
#### `Universal.Operators.DisallowLogicalAndOr` :bar_chart: :books:
Enforce the use of the boolean `&&` and `||` operators instead of the logical `and`/`or` operators.
@@ -391,6 +407,10 @@ Enforce no spaces around the union type and intersection type operators.
The available error codes are: `UnionTypeSpacesBefore`, `UnionTypeSpacesAfter`, `IntersectionTypeSpacesBefore`, `IntersectionTypeSpacesAfter`.
+#### `Universal.PHP.LowercasePHPTag` :wrench: :bar_chart: :books:
+
+Enforces that the "PHP" in a PHP open tag is lowercase.
+
#### `Universal.PHP.OneStatementInShortEchoTag` :wrench: :books:
Disallow short open echo tags `=` containing more than one PHP statement.
diff --git a/Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml b/Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml
new file mode 100644
index 00000000..953149b7
--- /dev/null
+++ b/Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ ! $b;
+
+if((bool) callMe($a)) {}
+ ]]>
+
+
+ ! ! $b;
+
+if(! ! ! callMe($a)) {}
+ ]]>
+
+
+
diff --git a/Universal/Docs/Operators/ConcatPositionStandard.xml b/Universal/Docs/Operators/ConcatPositionStandard.xml
new file mode 100644
index 00000000..4d2761af
--- /dev/null
+++ b/Universal/Docs/Operators/ConcatPositionStandard.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+ . $b . 'text'
+ . $c;
+ ]]>
+
+
+ .
+ $b . 'text'
+ . $c;
+ ]]>
+
+
+
diff --git a/Universal/Docs/PHP/LowercasePHPTagStandard.xml b/Universal/Docs/PHP/LowercasePHPTagStandard.xml
new file mode 100644
index 00000000..261f93cd
--- /dev/null
+++ b/Universal/Docs/PHP/LowercasePHPTagStandard.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Universal/Sniffs/CodeAnalysis/NoDoubleNegativeSniff.php b/Universal/Sniffs/CodeAnalysis/NoDoubleNegativeSniff.php
new file mode 100644
index 00000000..549c8cd3
--- /dev/null
+++ b/Universal/Sniffs/CodeAnalysis/NoDoubleNegativeSniff.php
@@ -0,0 +1,269 @@
+
+ */
+ private $operatorsWithLowerPrecedence;
+
+ /**
+ * Returns an array of tokens this test wants to listen for.
+ *
+ * @since 1.2.0
+ *
+ * @return array
+ */
+ public function register()
+ {
+ // Collect all the operators only once.
+ $this->operatorsWithLowerPrecedence = Tokens::$assignmentTokens;
+ $this->operatorsWithLowerPrecedence += Tokens::$booleanOperators;
+ $this->operatorsWithLowerPrecedence += Tokens::$comparisonTokens;
+ $this->operatorsWithLowerPrecedence += Tokens::$operators;
+ $this->operatorsWithLowerPrecedence[\T_INLINE_THEN] = \T_INLINE_THEN;
+ $this->operatorsWithLowerPrecedence[\T_INLINE_ELSE] = \T_INLINE_ELSE;
+
+ return [\T_BOOLEAN_NOT];
+ }
+
+ /**
+ * Processes this test, when one of its tokens is encountered.
+ *
+ * @since 1.2.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 int|void Integer stack pointer to skip forward or void to continue
+ * normal file processing.
+ */
+ public function process(File $phpcsFile, $stackPtr)
+ {
+ $tokens = $phpcsFile->getTokens();
+
+ $notCount = 1;
+ $lastNot = $stackPtr;
+ for ($afterNot = ($stackPtr + 1); $afterNot < $phpcsFile->numTokens; $afterNot++) {
+ if (isset(Tokens::$emptyTokens[$tokens[$afterNot]['code']])) {
+ continue;
+ }
+
+ if ($tokens[$afterNot]['code'] === \T_BOOLEAN_NOT) {
+ $lastNot = $afterNot;
+ ++$notCount;
+ continue;
+ }
+
+ break;
+ }
+
+ if ($notCount === 1) {
+ // Singular unary not-operator. Nothing to do.
+ return;
+ }
+
+ $found = \trim(GetTokensAsString::compact($phpcsFile, $stackPtr, $lastNot));
+ $data = [$found];
+
+ if (($notCount % 2) === 1) {
+ /*
+ * Oh dear... silly code time, found a triple negative (or other uneven number),
+ * this should just be a singular not-operator.
+ */
+ $fix = $phpcsFile->addFixableError(
+ 'Triple negative (or more) detected. Use a singular not (!) operator instead. Found: %s',
+ $stackPtr,
+ 'FoundTriple',
+ $data
+ );
+
+ if ($fix === true) {
+ $phpcsFile->fixer->beginChangeset();
+
+ $this->removeNotAndTrailingSpaces($phpcsFile, $stackPtr, $lastNot);
+
+ $phpcsFile->fixer->endChangeset();
+ }
+
+ // Only throw one error, even if there are more than two not-operators.
+ return $lastNot;
+ }
+
+ /*
+ * Found a double negative, which should be a boolean cast.
+ */
+
+ $fixable = true;
+
+ /*
+ * If whatever is being "cast" is within parentheses, we're good.
+ * If not, we need to prevent creating a change in behaviour
+ * when what follows is an `$x instanceof ...` expression, as
+ * the "instanceof" operator is right between a boolean cast
+ * and the ! operator precedence-wise.
+ *
+ * Note: this only applies to double negative, not triple negative.
+ *
+ * @link https://www.php.net/language.operators.precedence
+ */
+ if ($tokens[$afterNot]['code'] !== \T_OPEN_PARENTHESIS) {
+ $end = Parentheses::getLastCloser($phpcsFile, $stackPtr);
+ if ($end === false) {
+ $end = BCFile::findEndOfStatement($phpcsFile, $stackPtr);
+ }
+
+ for ($nextRelevant = $afterNot; $nextRelevant < $end; $nextRelevant++) {
+ if (isset(Tokens::$emptyTokens[$tokens[$nextRelevant]['code']])) {
+ continue;
+ }
+
+ if ($tokens[$nextRelevant]['code'] === \T_INSTANCEOF) {
+ $fixable = false;
+ break;
+ }
+
+ if (isset($this->operatorsWithLowerPrecedence[$tokens[$nextRelevant]['code']])) {
+ // The expression the `!` belongs to has ended.
+ break;
+ }
+
+ // Skip over anything within some form of brackets.
+ if (isset($tokens[$nextRelevant]['scope_closer'])
+ && ($nextRelevant === $tokens[$nextRelevant]['scope_opener']
+ || $nextRelevant === $tokens[$nextRelevant]['scope_condition'])
+ ) {
+ $nextRelevant = $tokens[$nextRelevant]['scope_closer'];
+ continue;
+ }
+
+ if (isset($tokens[$nextRelevant]['bracket_opener'], $tokens[$nextRelevant]['bracket_closer'])
+ && $nextRelevant === $tokens[$nextRelevant]['bracket_opener']
+ ) {
+ $nextRelevant = $tokens[$nextRelevant]['bracket_closer'];
+ continue;
+ }
+
+ if ($tokens[$nextRelevant]['code'] === \T_OPEN_PARENTHESIS
+ && isset($tokens[$nextRelevant]['parenthesis_closer'])
+ ) {
+ $nextRelevant = $tokens[$nextRelevant]['parenthesis_closer'];
+ continue;
+ }
+
+ // Skip over attributes (just in case).
+ if ($tokens[$nextRelevant]['code'] === \T_ATTRIBUTE
+ && isset($tokens[$nextRelevant]['attribute_closer'])
+ ) {
+ $nextRelevant = $tokens[$nextRelevant]['attribute_closer'];
+ continue;
+ }
+ }
+ }
+
+ $error = 'Double negative detected. Use a (bool) cast %s instead. Found: %s';
+ $code = 'FoundDouble';
+ $data = [
+ '',
+ $found,
+ ];
+
+ if ($fixable === false) {
+ $code = 'FoundDoubleWithInstanceof';
+ $data[0] = 'and parentheses around the instanceof expression';
+
+ // Don't auto-fix in combination with instanceof.
+ $phpcsFile->addError($error, $stackPtr, $code, $data);
+
+ // Only throw one error, even if there are more than two not-operators.
+ return $lastNot;
+ }
+
+ $fix = $phpcsFile->addFixableError($error, $stackPtr, $code, $data);
+
+ if ($fix === true) {
+ $phpcsFile->fixer->beginChangeset();
+
+ $this->removeNotAndTrailingSpaces($phpcsFile, $stackPtr, $lastNot);
+
+ $phpcsFile->fixer->replaceToken($lastNot, '(bool)');
+
+ $phpcsFile->fixer->endChangeset();
+ }
+
+ // Only throw one error, even if there are more than two not-operators.
+ return $lastNot;
+ }
+
+ /**
+ * Remove boolean not-operators and trailing whitespace after those,
+ * but don't remove comments or trailing whitespace after comments.
+ *
+ * @since 1.2.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.
+ * @param int $lastNot The position of the last boolean not token
+ * in the chain.
+ *
+ * @return void
+ */
+ private function removeNotAndTrailingSpaces(File $phpcsFile, $stackPtr, $lastNot)
+ {
+ $tokens = $phpcsFile->getTokens();
+ $ignore = false;
+
+ for ($i = $stackPtr; $i < $lastNot; $i++) {
+ if (isset(Tokens::$commentTokens[$tokens[$i]['code']])) {
+ // Ignore comments and whitespace after comments.
+ $ignore = true;
+ continue;
+ }
+
+ if ($tokens[$i]['code'] === \T_WHITESPACE && $ignore === false) {
+ $phpcsFile->fixer->replaceToken($i, '');
+ continue;
+ }
+
+ if ($tokens[$i]['code'] === \T_BOOLEAN_NOT) {
+ $ignore = false;
+ $phpcsFile->fixer->replaceToken($i, '');
+ }
+ }
+ }
+}
diff --git a/Universal/Sniffs/Operators/ConcatPositionSniff.php b/Universal/Sniffs/Operators/ConcatPositionSniff.php
new file mode 100644
index 00000000..093785ad
--- /dev/null
+++ b/Universal/Sniffs/Operators/ConcatPositionSniff.php
@@ -0,0 +1,204 @@
+
+ */
+ public function register()
+ {
+ return [\T_STRING_CONCAT];
+ }
+
+ /**
+ * Processes this test, when one of its tokens is encountered.
+ *
+ * @since 1.2.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 int|void Integer stack pointer to skip forward or void to continue
+ * normal file processing.
+ */
+ public function process(File $phpcsFile, $stackPtr)
+ {
+ /*
+ * Validate the setting.
+ */
+ if ($this->allowOnly !== self::POSITION_END) {
+ // Use the default.
+ $this->allowOnly = self::POSITION_START;
+ }
+
+ $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
+ $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
+
+ if ($nextNonEmpty === false) {
+ // Parse error/live coding.
+ return;
+ }
+
+ $tokens = $phpcsFile->getTokens();
+ if ($tokens[$prevNonEmpty]['line'] === $tokens[$nextNonEmpty]['line']) {
+ // Not multi-line concatenation. Not our target.
+ return;
+ }
+
+ $position = self::POSITION_STANDALONE;
+ if ($tokens[$prevNonEmpty]['line'] === $tokens[$stackPtr]['line']) {
+ $position = self::POSITION_END;
+ } elseif ($tokens[$nextNonEmpty]['line'] === $tokens[$stackPtr]['line']) {
+ $position = self::POSITION_START;
+ }
+
+ // Record metric.
+ $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, $position);
+
+ if ($this->allowOnly === $position) {
+ // All okay.
+ return;
+ }
+
+ $fix = $phpcsFile->addFixableError(
+ 'The concatenation operator for multi-line concatenations should always be at the %s of a line.',
+ $stackPtr,
+ 'Incorrect',
+ [$this->allowOnly]
+ );
+
+ if ($fix === true) {
+ if ($this->allowOnly === self::POSITION_END) {
+ $phpcsFile->fixer->beginChangeset();
+
+ // Move the concat operator.
+ $phpcsFile->fixer->replaceToken($stackPtr, '');
+ $phpcsFile->fixer->addContent($prevNonEmpty, ' .');
+
+ if ($position === self::POSITION_START
+ && $tokens[($stackPtr + 1)]['code'] === \T_WHITESPACE
+ ) {
+ // Remove trailing space.
+ $phpcsFile->fixer->replaceToken(($stackPtr + 1), '');
+ } elseif ($position === self::POSITION_STANDALONE) {
+ // Remove potential indentation space.
+ if ($tokens[($stackPtr - 1)]['code'] === \T_WHITESPACE) {
+ $phpcsFile->fixer->replaceToken(($stackPtr - 1), '');
+ }
+
+ // Remove new line.
+ if ($tokens[($stackPtr + 1)]['code'] === \T_WHITESPACE) {
+ $phpcsFile->fixer->replaceToken(($stackPtr + 1), '');
+ }
+ }
+
+ $phpcsFile->fixer->endChangeset();
+ return;
+ }
+
+ // Fixer for allowOnly === self::POSITION_START.
+ $phpcsFile->fixer->beginChangeset();
+
+ // Move the concat operator.
+ $phpcsFile->fixer->replaceToken($stackPtr, '');
+ $phpcsFile->fixer->addContentBefore($nextNonEmpty, '. ');
+
+ if ($position === self::POSITION_END
+ && $tokens[($stackPtr - 1)]['code'] === \T_WHITESPACE
+ ) {
+ // Remove trailing space.
+ $phpcsFile->fixer->replaceToken(($stackPtr - 1), '');
+ } elseif ($position === self::POSITION_STANDALONE) {
+ // Remove potential indentation space.
+ if ($tokens[($stackPtr - 1)]['code'] === \T_WHITESPACE) {
+ $phpcsFile->fixer->replaceToken(($stackPtr - 1), '');
+ }
+
+ // Remove new line.
+ if ($tokens[($stackPtr + 1)]['code'] === \T_WHITESPACE) {
+ $phpcsFile->fixer->replaceToken(($stackPtr + 1), '');
+ }
+ }
+
+ $phpcsFile->fixer->endChangeset();
+ }
+ }
+}
diff --git a/Universal/Sniffs/PHP/LowercasePHPTagSniff.php b/Universal/Sniffs/PHP/LowercasePHPTagSniff.php
new file mode 100644
index 00000000..b231b225
--- /dev/null
+++ b/Universal/Sniffs/PHP/LowercasePHPTagSniff.php
@@ -0,0 +1,87 @@
+
+ */
+ public function register()
+ {
+ return [\T_OPEN_TAG];
+ }
+
+ /**
+ * Processes this test, when one of its tokens is encountered.
+ *
+ * @since 1.2.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)
+ {
+ $tokens = $phpcsFile->getTokens();
+ $content = $tokens[$stackPtr]['content'];
+ $contentLC = \strtolower($content);
+
+ if ($contentLC === $content) {
+ $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'lowercase');
+ return;
+ }
+
+ $errorCode = '';
+ if (\strtoupper($content) === $content) {
+ $errorCode = 'Uppercase';
+ $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'uppercase');
+ } else {
+ $errorCode = 'Mixedcase';
+ $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'mixed case');
+ }
+
+ $fix = $phpcsFile->addFixableError(
+ 'The php open tag should be in lowercase. Found: %s',
+ $stackPtr,
+ $errorCode,
+ [\trim($content)]
+ );
+
+ if ($fix === true) {
+ $phpcsFile->fixer->replaceToken($stackPtr, $contentLC);
+ }
+ }
+}
diff --git a/Universal/Sniffs/UseStatements/NoUselessAliasesSniff.php b/Universal/Sniffs/UseStatements/NoUselessAliasesSniff.php
index 6d699934..93de596c 100644
--- a/Universal/Sniffs/UseStatements/NoUselessAliasesSniff.php
+++ b/Universal/Sniffs/UseStatements/NoUselessAliasesSniff.php
@@ -28,15 +28,6 @@
final class NoUselessAliasesSniff implements Sniff
{
- /**
- * Name of the "Use import source" metric.
- *
- * @since 1.1.0
- *
- * @var string
- */
- const METRIC_NAME = 'Import use statement type';
-
/**
* Returns an array of tokens this test wants to listen for.
*
diff --git a/Universal/Tests/CodeAnalysis/NoDoubleNegativeUnitTest.inc b/Universal/Tests/CodeAnalysis/NoDoubleNegativeUnitTest.inc
new file mode 100644
index 00000000..dd8d8712
--- /dev/null
+++ b/Universal/Tests/CodeAnalysis/NoDoubleNegativeUnitTest.inc
@@ -0,0 +1,104 @@
+prop instanceof Something);
+$var = !!!($something->method() instanceof Something);
+$var = !!!!($something['key']->my['key']->chained instanceof Something);
+
+$var = !!(self::ENUM_SET_AS_CONSTANT instanceof \Something);
+$var = !!(parent::ENUM_SET_AS_CONSTANT instanceof Something);
+$var = !! (static::ENUM_SET_AS_CONSTANT instanceof \Something);
+
+if (!!
+ (function ($p) {
+ return new $p;
+})('dummy') instanceof Foo) {}
+
+// If an instanceof is after another operator with lower precedence, we're good.
+if (!!$something && $somethingElse instanceof \Something) {}
+if (!!$something() || $somethingElse instanceof \Something) {}
+if (!! $boolean !== $somethingElse instanceof Something) {}
+while (!! $boolean = $somethingElse instanceof \Something) {}
+if ( ! ! $boolean and $somethingElse instanceof \Something) {}
+$var = !!$boolean ? $somethingElse instanceof Something : true;
+$var = !!$boolean ?? $somethingElse instanceof \Something;
+
+// Instanceof where the not does not apply to.
+if (!!$something($somethingElse instanceof \Something)) {}
+if (!!['key' => $somethingElse instanceof \Something]['key']) {}
+if (!!$array[$somethingElse instanceof \Something ? 'keyA' : 'keyB']) {}
+if (!!match($a) { 'foo' => $a instanceof Foo, 'bar' => $a instanceof Bar } === true) {}
+
+// Note: this contains an intentional parse error, nothing to worry about.
+if (!!
+ #[\Attr(self::ENUM_SET_AS_CONSTANT instanceof Foo)]
+ (function ($p) {
+ return $p instanceof Foo;
+})('dummy')) {}
+
+// If it's an instanceof with triple not, we're good.
+$var = !!!$something->method() instanceof \Something;
+
+// Make the error non-autofixable when used in combination with instanceof without parentheses to avoid issues with precedence.
+if(!!$something instanceof \Something) {}
+
+$var = !!$something->prop instanceof \Something;
+
+$var = !!self::ENUM_SET_AS_CONSTANT instanceof \Something;
+$var = (!!parent::ENUM_SET_AS_CONSTANT instanceof \Something);
+$var = !! static::ENUM_SET_AS_CONSTANT instanceof \Something;
+
+$var = (!!!!$something['key']->my['key']->chained instanceof \Something);
+
+$var = !!$something[$a + 1] instanceof \Something;
+$var = !!$something[++$a] instanceof \Something;
+
+$var = !!callMe($something[$a + 1]) instanceof \Something;
+$var = !!new ClassA instanceof ('std' . 'Class');
+
+if (!!match($a) { 'foo' => new Foo, 'bar' => new Bar } instanceof Bar) {}
+
+// Intentional parse error/live coding.
+// This needs to be the last test in the file.
+if(!
diff --git a/Universal/Tests/CodeAnalysis/NoDoubleNegativeUnitTest.inc.fixed b/Universal/Tests/CodeAnalysis/NoDoubleNegativeUnitTest.inc.fixed
new file mode 100644
index 00000000..185f9194
--- /dev/null
+++ b/Universal/Tests/CodeAnalysis/NoDoubleNegativeUnitTest.inc.fixed
@@ -0,0 +1,101 @@
+prop instanceof Something);
+$var = !($something->method() instanceof Something);
+$var = (bool)($something['key']->my['key']->chained instanceof Something);
+
+$var = (bool)(self::ENUM_SET_AS_CONSTANT instanceof \Something);
+$var = (bool)(parent::ENUM_SET_AS_CONSTANT instanceof Something);
+$var = (bool) (static::ENUM_SET_AS_CONSTANT instanceof \Something);
+
+if ((bool)
+ (function ($p) {
+ return new $p;
+})('dummy') instanceof Foo) {}
+
+// If an instanceof is after another operator with lower precedence, we're good.
+if ((bool)$something && $somethingElse instanceof \Something) {}
+if ((bool)$something() || $somethingElse instanceof \Something) {}
+if ((bool) $boolean !== $somethingElse instanceof Something) {}
+while ((bool) $boolean = $somethingElse instanceof \Something) {}
+if ( (bool) $boolean and $somethingElse instanceof \Something) {}
+$var = (bool)$boolean ? $somethingElse instanceof Something : true;
+$var = (bool)$boolean ?? $somethingElse instanceof \Something;
+
+// Instanceof where the not does not apply to.
+if ((bool)$something($somethingElse instanceof \Something)) {}
+if ((bool)['key' => $somethingElse instanceof \Something]['key']) {}
+if ((bool)$array[$somethingElse instanceof \Something ? 'keyA' : 'keyB']) {}
+if ((bool)match($a) { 'foo' => $a instanceof Foo, 'bar' => $a instanceof Bar } === true) {}
+
+// Note: this contains an intentional parse error, nothing to worry about.
+if ((bool)
+ #[\Attr(self::ENUM_SET_AS_CONSTANT instanceof Foo)]
+ (function ($p) {
+ return $p instanceof Foo;
+})('dummy')) {}
+
+// If it's an instanceof with triple not, we're good.
+$var = !$something->method() instanceof \Something;
+
+// Make the error non-autofixable when used in combination with instanceof without parentheses to avoid issues with precedence.
+if(!!$something instanceof \Something) {}
+
+$var = !!$something->prop instanceof \Something;
+
+$var = !!self::ENUM_SET_AS_CONSTANT instanceof \Something;
+$var = (!!parent::ENUM_SET_AS_CONSTANT instanceof \Something);
+$var = !! static::ENUM_SET_AS_CONSTANT instanceof \Something;
+
+$var = (!!!!$something['key']->my['key']->chained instanceof \Something);
+
+$var = !!$something[$a + 1] instanceof \Something;
+$var = !!$something[++$a] instanceof \Something;
+
+$var = !!callMe($something[$a + 1]) instanceof \Something;
+$var = !!new ClassA instanceof ('std' . 'Class');
+
+if (!!match($a) { 'foo' => new Foo, 'bar' => new Bar } instanceof Bar) {}
+
+// Intentional parse error/live coding.
+// This needs to be the last test in the file.
+if(!
diff --git a/Universal/Tests/CodeAnalysis/NoDoubleNegativeUnitTest.php b/Universal/Tests/CodeAnalysis/NoDoubleNegativeUnitTest.php
new file mode 100644
index 00000000..ec377b1e
--- /dev/null
+++ b/Universal/Tests/CodeAnalysis/NoDoubleNegativeUnitTest.php
@@ -0,0 +1,86 @@
+ Key is the line number, value is the number of expected errors.
+ */
+ public function getErrorList()
+ {
+ return [
+ 16 => 1,
+ 18 => 1,
+ 19 => 1,
+ 20 => 1,
+ 21 => 1,
+ 22 => 1,
+ 24 => 1,
+ 30 => 1,
+ 34 => 1,
+ 43 => 1,
+ 45 => 1,
+ 46 => 1,
+ 47 => 1,
+ 49 => 1,
+ 50 => 1,
+ 51 => 1,
+ 53 => 1,
+ 59 => 1,
+ 60 => 1,
+ 61 => 1,
+ 62 => 1,
+ 63 => 1,
+ 64 => 1,
+ 65 => 1,
+ 68 => 1,
+ 69 => 1,
+ 70 => 1,
+ 71 => 1,
+ 74 => 1,
+ 81 => 1,
+ 84 => 1,
+ 86 => 1,
+ 88 => 1,
+ 89 => 1,
+ 90 => 1,
+ 92 => 1,
+ 94 => 1,
+ 95 => 1,
+ 97 => 1,
+ 98 => 1,
+ 100 => 1,
+ ];
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @return array Key is the line number, value is the number of expected warnings.
+ */
+ public function getWarningList()
+ {
+ return [];
+ }
+}
diff --git a/Universal/Tests/Operators/ConcatPositionUnitTest.inc b/Universal/Tests/Operators/ConcatPositionUnitTest.inc
new file mode 100644
index 00000000..24569e7a
--- /dev/null
+++ b/Universal/Tests/Operators/ConcatPositionUnitTest.inc
@@ -0,0 +1,122 @@
+ Key is the line number, value is the number of expected errors.
+ */
+ public function getErrorList()
+ {
+ return [
+ 30 => 1,
+ 36 => 1,
+ 37 => 1,
+ 40 => 1,
+ 43 => 1,
+ 44 => 1,
+ 47 => 1,
+ 49 => 1,
+ 54 => 1,
+ 81 => 1,
+ 84 => 1,
+ 85 => 1,
+ 88 => 1,
+ 89 => 1,
+ 93 => 1,
+ 95 => 1,
+ 98 => 1,
+ 113 => 1,
+ ];
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @return array Key is the line number, value is the number of expected warnings.
+ */
+ public function getWarningList()
+ {
+ return [];
+ }
+}
diff --git a/Universal/Tests/PHP/LowercasePHPTagUnitTest.inc b/Universal/Tests/PHP/LowercasePHPTagUnitTest.inc
new file mode 100644
index 00000000..c06d7659
--- /dev/null
+++ b/Universal/Tests/PHP/LowercasePHPTagUnitTest.inc
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/Universal/Tests/PHP/LowercasePHPTagUnitTest.inc.fixed b/Universal/Tests/PHP/LowercasePHPTagUnitTest.inc.fixed
new file mode 100644
index 00000000..51cef43b
--- /dev/null
+++ b/Universal/Tests/PHP/LowercasePHPTagUnitTest.inc.fixed
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/Universal/Tests/PHP/LowercasePHPTagUnitTest.php b/Universal/Tests/PHP/LowercasePHPTagUnitTest.php
new file mode 100644
index 00000000..87a824b0
--- /dev/null
+++ b/Universal/Tests/PHP/LowercasePHPTagUnitTest.php
@@ -0,0 +1,48 @@
+ Key is the line number, value is the number of expected errors.
+ */
+ public function getErrorList()
+ {
+ return [
+ 4 => 1,
+ 7 => 1,
+ 8 => 1,
+ ];
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @return array Key is the line number, value is the number of expected warnings.
+ */
+ public function getWarningList()
+ {
+ return [];
+ }
+}
diff --git a/composer.json b/composer.json
index f4bbf2b9..b59f2d8e 100644
--- a/composer.json
+++ b/composer.json
@@ -17,7 +17,8 @@
],
"support" : {
"issues" : "https://github.com/PHPCSStandards/PHPCSExtra/issues",
- "source" : "https://github.com/PHPCSStandards/PHPCSExtra"
+ "source" : "https://github.com/PHPCSStandards/PHPCSExtra",
+ "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy"
},
"require" : {
"php" : ">=5.4",
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index ba322bbb..060dda93 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -1,5 +1,5 @@
parameters:
- #phpVersion: 50400 # Needs to be 70100 or higher... sigh...
+ # phpVersion: 50400 # Needs to be 70100 or higher... sigh...
level: 6
paths:
- Modernize
@@ -10,6 +10,8 @@ parameters:
treatPhpDocTypesAsCertain: false
ignoreErrors:
+ # yamllint disable rule:line-length
+
# Level 1
# Keep to stay in line with parent class.
-
@@ -22,21 +24,21 @@ parameters:
-
message: '`^Property \S+Sniff::\$(phpVersion|tabWidth) \(int\) in isset\(\) is not nullable\.$`'
paths:
- - Modernize\Sniffs\FunctionCalls\DirnameSniff.php
- - Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php
- - Universal\Sniffs\CodeAnalysis\ConstructorDestructorReturnSniff.php
- - Universal\Sniffs\WhiteSpace\CommaSpacingSniff.php
- - Universal\Sniffs\WhiteSpace\DisallowInlineTabsSniff.php
- - Universal\Sniffs\WhiteSpace\PrecisionAlignmentSniff.php
+ - Modernize\Sniffs\FunctionCalls\DirnameSniff.php
+ - Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php
+ - Universal\Sniffs\CodeAnalysis\ConstructorDestructorReturnSniff.php
+ - Universal\Sniffs\WhiteSpace\CommaSpacingSniff.php
+ - Universal\Sniffs\WhiteSpace\DisallowInlineTabsSniff.php
+ - Universal\Sniffs\WhiteSpace\PrecisionAlignmentSniff.php
-
message: '`^Strict comparison using === between true and false will always evaluate to false\.$`'
paths:
- - Modernize\Sniffs\FunctionCalls\DirnameSniff.php
- - Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php
- - Universal\Sniffs\CodeAnalysis\ConstructorDestructorReturnSniff.php
- - Universal\Sniffs\WhiteSpace\CommaSpacingSniff.php
- - Universal\Sniffs\WhiteSpace\DisallowInlineTabsSniff.php
- - Universal\Sniffs\WhiteSpace\PrecisionAlignmentSniff.php
+ - Modernize\Sniffs\FunctionCalls\DirnameSniff.php
+ - Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php
+ - Universal\Sniffs\CodeAnalysis\ConstructorDestructorReturnSniff.php
+ - Universal\Sniffs\WhiteSpace\CommaSpacingSniff.php
+ - Universal\Sniffs\WhiteSpace\DisallowInlineTabsSniff.php
+ - Universal\Sniffs\WhiteSpace\PrecisionAlignmentSniff.php
-
message: '`^Property PHPCSExtra\\Universal\\Sniffs\\Arrays\\DuplicateArrayKeySniff\:\:\$currentMaxIntKey[GL]t8 \(int\) in isset\(\) is not nullable\.$`'
path: Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php
@@ -50,3 +52,5 @@ parameters:
# We're not using strict types, so this will be juggled without any issues.
- '#^Parameter \#3 \$value of method \S+File::recordMetric\(\) expects string, \(?(float|int|bool)(<[^>]+>)?(\|(float|int|bool)(<[^>]+>)?)*\)? given\.$#'
- '#^Parameter \#2 \$content of method \S+Fixer::replaceToken\(\) expects string, \(?(float|int|bool)(<[^>]+>)?(\|(float|int|bool)(<[^>]+>)?)*\)? given\.$#'
+
+ # yamllint enable rule:line-length