diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist
index 612a0033..17542c91 100644
--- a/.phpcs.xml.dist
+++ b/.phpcs.xml.dist
@@ -32,4 +32,12 @@
+
+
+
+
+
+
+
+
diff --git a/.travis.yml b/.travis.yml
index 23a24ac6..0f080913 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,4 @@
-sudo: false
-
-language:
- - php
+language: php
dist: trusty
@@ -26,24 +23,58 @@ php:
env:
# Test against the highest/lowest supported PHPCS and WPCS versions.
- PHPCS_BRANCH="dev-master" WPCS="dev-develop" PHPLINT=1
- - PHPCS_BRANCH="dev-master" WPCS="2.1.1"
- - PHPCS_BRANCH="3.4.2" WPCS="dev-develop"
- - PHPCS_BRANCH="3.4.2" WPCS="2.1.1"
+ - PHPCS_BRANCH="dev-master" WPCS="2.2.0"
+ - PHPCS_BRANCH="3.5.0" WPCS="dev-develop"
+ - PHPCS_BRANCH="3.5.0" WPCS="2.2.0"
+
+# Define the stages used.
+# For non-PRs, only the sniff, ruleset and quicktest stages are run.
+# For pull requests and merges, the full script is run (skipping quicktest).
+# Note: for pull requests, "develop" should be 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)
matrix:
fast_finish: true
include:
- # Extra build to check for XML codestyle.
- - php: 7.1
- env: PHPCS_BRANCH="dev-master" WPCS="^2.1.1" SNIFF=1
+ #### SNIFF STAGE ####
+ - stage: sniff
+ php: 7.3
+ env: PHPCS_BRANCH="dev-master" WPCS="dev-master"
addons:
- apt:
- packages:
- - libxml2-utils
+ apt:
+ packages:
+ - libxml2-utils
+ script:
+ # Check the codestyle of the files within YoastCS.
+ - composer check-cs
+
+ # Validate the xml files.
+ # @link http://xmlsoft.org/xmllint.html
+ # For the build to properly error when validating against a scheme, these each have to be in their own condition.
+ - xmllint --noout --schema ./vendor/squizlabs/php_codesniffer/phpcs.xsd ./Yoast/ruleset.xml
+ - xmllint --noout ./Yoast/Docs/*/*Standard.xml
- allow_failures:
- # Allow failures for unstable builds.
- - php: "7.4snapshot"
+ # Check the code-style consistency of the xml files.
+ - diff -B --tabsize=4 ./Yoast/ruleset.xml <(xmllint --format "./Yoast/ruleset.xml")
+
+ #### QUICK TEST STAGE ####
+ # This is a much quicker test which only runs the unit tests and linting against low/high
+ # supported PHP/PHPCS/WPCS combinations.
+ - stage: quicktest
+ php: 7.3
+ env: PHPCS_BRANCH="dev-master" WPCS="dev-develop" PHPLINT=1
+ - php: 7.3
+ env: PHPCS_BRANCH="3.5.0" WPCS="2.2.0"
+ - php: 5.4
+ env: PHPCS_BRANCH="dev-master" WPCS="2.2.0" PHPLINT=1
+ - php: 5.4
+ env: PHPCS_BRANCH="3.5.0" WPCS="dev-develop"
before_install:
# Speed up build time by disabling Xdebug.
@@ -54,11 +85,9 @@ before_install:
# 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 [[ $PHPCS_BRANCH != "dev-master" && $WPCS != "dev-develop" ]]; then
+ if [[ "$TRAVIS_BUILD_STAGE_NAME" != "Sniff" && $PHPCS_BRANCH != "dev-master" && $WPCS != "dev-develop" ]]; then
echo 'error_reporting = E_ALL & ~E_DEPRECATED' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
fi
- - php -r "echo ini_get('error_reporting');"
-
- export XMLLINT_INDENT=" "
- export PHPCS_DIR=$(pwd)/vendor/squizlabs/php_codesniffer
@@ -68,36 +97,21 @@ before_install:
# Set the WPCS version to test against.
- composer require wp-coding-standards/wpcs:${WPCS} --no-update --no-suggest --no-scripts
- |
- if [[ "$SNIFF" == "1" ]]; then
- composer install --dev --no-suggest
- # The DealerDirect Composer plugin script takes care of the installed_paths.
- else
+ if [[ "$TRAVIS_BUILD_STAGE_NAME" != "Sniff" ]]; then
# For testing the YoastCS native sniffs, the rest of the packages aren't needed.
- composer remove phpcompatibility/phpcompatibility-wp --no-update
- # The Travis images for PHP >= 7.2 ship with PHPUnit 8, but the unit test suite is not compatible with that.
- if [[ ${TRAVIS_PHP_VERSION:0:3} > "7.1" ]]; then composer require phpunit/phpunit:^7.0 --no-update --no-suggest --no-scripts;fi
- # This will now only install the version of PHPCS/WPCS to test against.
- composer install --no-dev --no-suggest
- # The DealerDirect PHPCS Composer plugin takes care of the installed_paths.
+ composer remove phpcompatibility/phpcompatibility-wp phpcompatibility/php-compatibility --no-update
fi
+ - composer install --dev --no-suggest
+ # The DealerDirect Composer plugin script takes care of the installed_paths.
- $PHPCS_BIN -i
script:
- - if [[ "$PHPLINT" == "1" ]]; then if find . -path ./vendor -prune -o -name "*.php" -exec php -l {} \; | grep "^[Parse error|Fatal error]"; then exit 1; fi; fi
- - |
- if [[ ${TRAVIS_PHP_VERSION:0:3} > "7.1" ]]; then
- vendor/bin/phpunit --filter Yoast --bootstrap="$PHPCS_DIR/tests/bootstrap.php" $PHPCS_DIR/tests/AllTests.php
- else
- phpunit --filter Yoast --bootstrap="$PHPCS_DIR/tests/bootstrap.php" $PHPCS_DIR/tests/AllTests.php
- fi
- # Check the codestyle of the files within YoastCS.
- - if [[ "$SNIFF" == "1" ]]; then composer check-cs; fi
- # Validate the xml files.
- # @link http://xmlsoft.org/xmllint.html
- - if [[ "$SNIFF" == "1" ]]; then xmllint --noout --schema ./vendor/squizlabs/php_codesniffer/phpcs.xsd ./Yoast/ruleset.xml; fi
- - if [[ "$SNIFF" == "1" ]]; then xmllint --noout ./Yoast/Docs/*/*Standard.xml; fi
- # Check the code-style consistency of the xml files.
- - if [[ "$SNIFF" == "1" ]]; then diff -B --tabsize=4 ./Yoast/ruleset.xml <(xmllint --format "./Yoast/ruleset.xml"); fi
+ # Lint the PHP files against parse errors.
+ - if [[ "$PHPLINT" == "1" ]]; then composer lint; fi
+
+ # Run the unit tests.
+ - vendor/bin/phpunit --filter Yoast --bootstrap="$PHPCS_DIR/tests/bootstrap.php" $PHPCS_DIR/tests/AllTests.php
+
# Validate the composer.json file.
# @link https://getcomposer.org/doc/03-cli.md#validate
- if [[ "$PHPLINT" == "1" ]]; then composer validate --no-check-all --strict; fi
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e7b4254..bdb49937 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,72 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/).
+### [2.0.0] - 2019-12-17
+
+#### Added
+* PHPCS: New `Yoast.NamingConventions.ObjectNameDepth` sniff.
+ - For objects _declared within a namespace_, this sniff verifies that an object name consist of maximum three words separated by underscores.
+ - For objects which are part of a unit test suite, a `_Test`, `_Mock` or `_Double` suffix at the end of the object name will be disregarded for the purposes of the word count.
+ - The sniff has two configurable properties `max_words` (error) and `recommended_max_words` (warning). The default for both is `3`.
+* PHPCS: New `Yoast.NamingConventions.NamespaceName` sniff.
+ This sniff verifies that:
+ - Namespace names consist of a maximum of three levels (excluding the plugin specific prefix) and recommends for the name to be maximum two levels deep.
+ For unit test files, `Tests\(Doubles\)` directly after the prefix will be ignored when determining the level depth.
+ - The levels in the namespace name directly translate to the directory path to the file.
+ - The sniff has four configurable properties:
+ - `max_levels` (error) and `recommended_max_levels` (warning) which are by default set to `3` and `2` respectively.
+ - `src_directory` to indicate the project root(s) for the _path-to-name_ translation when the project root is not the repo root directory.
+ - `prefixes` to set the plugin specific prefix(es) to take into account.
+* PHPCS: New `Yoast.NamingConventions.ValidHookName` sniff.
+ This sniff extends and adds to the upstream `WordPress.NamingConventions.ValidHookName` sniff.
+ The sniff will ignore non-prefixed hooks and hooks with a prefix unrelated to the plugin being examined, to prevent errors being thrown about hook names which are outside of our control.
+ This sniff verifies that:
+ - Hook names are in lowercase with words separated by underscores (same as WordPressCS).
+ - Hook names are prefixed with the plugin specific prefix in namespace format, i.e. `Yoast\WP\PluginName`.
+ Note: The prefix is exempt from the _lowercase with words separated by underscores_ rule.
+ If the non-namespace type prefix for a plugin is used, the sniff will throw a `warning`.
+ - The actual hook name (after the prefix) consist of maximum four words separated by underscores.
+ - Note: _The hook_name part should be descriptive for the (dev-)user and does not need to follow the namespace or file path of the file they are in._
+ - Also note: for dynamic hook names where the hook name length can not reliably be determined, the sniff will throw a `warning` at severity `3` suggesting the hook name be inspected manually.
+ As the default `severity` for PHPCS is `5`, this `warning` at severity `3` will normally not be shown.
+ It is there to allow for intermittently checking of the dynamic hook names. To trigger it, `--severity=3` should be passed on the command line.
+ - The sniff has three configurable properties:
+ - `maximum_depth` (error) and `soft_maximum_depth` (warning). The default for both is `4`.
+ - `prefixes` to set the plugin specific prefix(es) to take into account.
+* PHPCS: The `Generic.Arrays.DisallowLongArraySyntax` sniff.
+ WPCS 2.2.0 demands long array syntax. In contrast to that, YoastCS demands short array syntax.
+* PHPCS: The `Generic.ControlStructures.DisallowYodaConditions` sniff.
+ In contrast to WPCS, YoastCS never demanded Yoda conditions. With the addition of this sniff, "normal" (non-Yoda) conditions will now be enforced.
+* PHPCS: The `Generic.WhiteSpace.SpreadOperatorSpacingAfter` sniff.
+ Enforces no space between the `...` spread operator and the variable/function call it applies to.
+* PHPCS: The `PEAR.WhiteSpace.ObjectOperatorIndent` sniff.
+ Enforce consistent indentation of chained method calls to one more or less than the previous call in the chain and always at least one in from the start of the chain.
+* PHPCS: The `PSR12.Classes.ClosingBrace` sniff.
+ This sniff disallows the outdated practice of `// end ...` comments for OO stuctures.
+* PHPCS: The `PSR12.Files.ImportStatement` sniff.
+ Import `use` statements must always be fully qualified, so a leading backslash is redundant (and discouraged by PHP itself).
+ This sniff enforces that no leading backslash is used for import `use` statements.
+* PHPCS: The `PSR12.Files.OpenTag` sniff.
+ Enforces that a PHP open tag is on a line by itself in PHP-only files.
+* PHPCS: A `CustomPrefixesTrait` to handle checking names against a list of custom prefixes.
+* Composer: `lint` script which uses the [Parallel-Lint] package for faster and more readable linting results.
+
+#### Changed
+* :warning: PHPCS: `Yoast.Files.FileName` sniff: the public `$prefixes` property, which can be used to indicate which _prefixes_ should be stripped of a class name when translating it to a file name, has been renamed to `$oo_prefixes`.
+ Custom repo specific rulesets using the property should be updates to reflect this change.
+* :warning: PHPCS: `Yoast.Files.FileName` sniff: the public `$exclude` property, which can be used to indicate which files to exclude from the file name versus object name check, has been renamed to `$excluded_files_strict_check`.
+ Custom repo specific rulesets using the property should be updates to reflect this change.
+* PHPCS: The default setting for the minimum supported PHP version for repos using YoastCS is now PHP 5.6 (was 5.2).
+* PHPCS: The default value for the `minimum_supported_wp_version` property which is used by various WPCS sniffs has been update to WP `5.2` (was `4.9`).
+* Composer: Supported version of [PHP_CodeSniffer] has been changed from `^3.4.2` to `^3.5.0`.
+ Note: this makes the option `--filter=gitstaged` available which can be used in git `pre-commit` hooks to only check staged files.
+* Composer: Supported version of [WordPressCS] has been changed from `^2.1.1` to `^2.2.0`.
+* Composer: Supported version of [PHPCompatibilityWP] has been changed from `^2.0.0` to `^2.1.0`.
+* Travis: the build check is now run in stages.
+* Travis: Tests against PHP 7.4 are no longer allowed to fail.
+* Various housekeeping & code compliance with YoastCS 2.0.0.
+
+
### [1.3.0] - 2019-07-31
#### Added
@@ -285,12 +351,14 @@ Initial public release as a stand-alone package.
[PHP_CodeSniffer]: https://github.com/squizlabs/PHP_CodeSniffer/releases
-[WordPressCS]: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/blob/develop/CHANGELOG.md
+[WordPressCS]: https://github.com/WordPress/WordPress-Coding-Standards/blob/develop/CHANGELOG.md
[PHPCompatibilityWP]: https://github.com/PHPCompatibility/PHPCompatibilityWP#changelog
[PHPCompatibility]: https://github.com/PHPCompatibility/PHPCompatibility/blob/master/CHANGELOG.md
[PHP Mess Detector]: https://github.com/phpmd/phpmd/blob/master/CHANGELOG
[DealerDirect Composer PHPCS plugin]: https://github.com/Dealerdirect/phpcodesniffer-composer-installer/releases
+[Parallel-Lint]: https://packagist.org/packages/jakub-onderka/php-parallel-lint
+[2.0.0]: https://github.com/Yoast/yoastcs/compare/1.3.0...2.0.0
[1.3.0]: https://github.com/Yoast/yoastcs/compare/1.2.2...1.3.0
[1.2.2]: https://github.com/Yoast/yoastcs/compare/1.2.1...1.2.2
[1.2.1]: https://github.com/Yoast/yoastcs/compare/1.2.0...1.2.1
diff --git a/README.md b/README.md
index 525f1a84..b52c57ab 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ Severity levels:
### The YoastCS Standard
The `Yoast` standard for PHP_CodeSniffer is comprised of the following:
-* The `WordPress` ruleset from the [WordPress Coding Standards](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards) implementing the official [WordPress PHP Coding Standards](https://make.wordpress.org/core/handbook/coding-standards/php/), with some [select exclusions](https://github.com/Yoast/yoastcs/blob/develop/Yoast/ruleset.xml#L29-L75).
+* The `WordPress` ruleset from the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) implementing the official [WordPress PHP Coding Standards](https://make.wordpress.org/core/handbook/coding-standards/php/), with some [select exclusions](https://github.com/Yoast/yoastcs/blob/develop/Yoast/ruleset.xml#L29-L75).
* The [`PHPCompatibilityWP`](https://github.com/PHPCompatibility/PHPCompatibilityWP) ruleset which checks code for PHP cross-version compatibility while preventing false positives for functionality polyfilled within WordPress.
* Select additional sniffs taken from [`PHP_CodeSniffer`](https://github.com/squizlabs/PHP_CodeSniffer).
* A number of custom Yoast specific sniffs.
diff --git a/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml b/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml
new file mode 100644
index 00000000..a7982e10
--- /dev/null
+++ b/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+ Admin\Forms;
+
+namespace Yoast\WP\Plugin\Tests\Admin\Forms;
+ ]]>
+
+
+ Admin\Forms\Type\Sub;
+
+namespace Yoast\WP\Plugin\Tests\Foo\Bar\Flo\Sub;
+ ]]>
+
+
+
+
+
+
+
+ admin/forms/file.php -->
+Admin\Forms;
+ ]]>
+
+
+ admin/forms/file.php -->
+Unrelated;
+ ]]>
+
+
+
diff --git a/Yoast/Docs/NamingConventions/ObjectNameDepthStandard.xml b/Yoast/Docs/NamingConventions/ObjectNameDepthStandard.xml
new file mode 100644
index 00000000..513a3d7b
--- /dev/null
+++ b/Yoast/Docs/NamingConventions/ObjectNameDepthStandard.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+ Non_Namespaced_Long_Class_Name {}
+ ]]>
+
+
+ Namespaced_Long_Class_Name {}
+ ]]>
+
+
+
+
+ Short_Class_Name {}
+ ]]>
+
+
+ Namespaced_Too_Long_Class_Name {}
+ ]]>
+
+
+
+
+ Short_Class_Name_Test
+ extends TestCase {}
+ ]]>
+
+
+ Namespaced_Too_Long_Class_Name_Test
+ extends TestCase {}
+ ]]>
+
+
+
diff --git a/Yoast/Docs/NamingConventions/ValidHookNameStandard.xml b/Yoast/Docs/NamingConventions/ValidHookNameStandard.xml
new file mode 100644
index 00000000..4761d4fa
--- /dev/null
+++ b/Yoast/Docs/NamingConventions/ValidHookNameStandard.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+ 'Yoast\WP\Plugin\hook_name', $var );
+ ]]>
+
+
+ 'Yoast\WP\Plugin\Hook_NAME', $var );
+ ]]>
+
+
+
+
+ 'Yoast\WP\Plugin\hook_name',
+ $var
+);
+ ]]>
+
+
+ 'Yoast\WP\Plugin\some/hook-name',
+ $var
+);
+ ]]>
+
+
+
+
+
+
+
+ 'Yoast\WP\Plugin\hook_name'
+);
+ ]]>
+
+
+ 'Yoast\WP\Plugin\long_hook_name_too_long'
+);
+ ]]>
+
+
+
diff --git a/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php b/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php
index 6738f8b7..7da47be2 100644
--- a/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php
+++ b/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php
@@ -23,9 +23,9 @@ class CodeCoverageIgnoreDeprecatedSniff implements Sniff {
* @return array
*/
public function register() {
- return array(
+ return [
T_FUNCTION,
- );
+ ];
}
/**
diff --git a/Yoast/Sniffs/Commenting/CoversTagSniff.php b/Yoast/Sniffs/Commenting/CoversTagSniff.php
index 3ff0fd55..173ca4b0 100644
--- a/Yoast/Sniffs/Commenting/CoversTagSniff.php
+++ b/Yoast/Sniffs/Commenting/CoversTagSniff.php
@@ -45,9 +45,9 @@ class CoversTagSniff implements Sniff {
* @return array
*/
public function register() {
- return array(
+ return [
T_DOC_COMMENT_OPEN_TAG,
- );
+ ];
}
/**
@@ -148,17 +148,17 @@ public function process( File $phpcsFile, $stackPtr ) {
// Throw a generic error for all other invalid annotations.
$error = self::ERROR_MSG;
$error .= ' Check the PHPUnit documentation to see which annotations are supported. Found: %s';
- $data = array( $annotation );
+ $data = [ $annotation ];
$phpcsFile->addError( $error, $next, 'Invalid', $data );
}
$coversNothingCount = count( $coversNothingTags );
if ( $firstCoversTag !== false && $coversNothingCount > 0 ) {
$error = 'A test can\'t both cover something as well as cover nothing. First @coversNothing tag encountered on line %d; first @covers tag encountered on line %d';
- $data = array(
+ $data = [
$tokens[ $coversNothingTags[0] ]['line'],
$tokens[ $firstCoversTag ]['line'],
- );
+ ];
$phpcsFile->addError( $error, $tokens[ $stackPtr ]['comment_closer'], 'Contradictory', $data );
}
@@ -167,7 +167,7 @@ public function process( File $phpcsFile, $stackPtr ) {
$error = 'Only one @coversNothing tag allowed per test';
$code = 'DuplicateCoversNothing';
$fixable = true;
- $removeTags = array();
+ $removeTags = [];
foreach ( $coversNothingTags as $position => $ptr ) {
$next = ( $ptr + 1 );
if ( $tokens[ $next ]['code'] === T_DOC_COMMENT_WHITESPACE
@@ -232,7 +232,7 @@ public function process( File $phpcsFile, $stackPtr ) {
if ( ! isset( $first ) ) {
$first = explode( '-', $ptrs );
- $data = array( $tokens[ $first[0] ]['line'] );
+ $data = [ $tokens[ $first[0] ]['line'] ];
continue;
}
@@ -288,10 +288,10 @@ protected function fixSimpleError( File $phpcsFile, $stackPtr, $expected, $error
}
$error = self::ERROR_MSG . "\nExpected: `%s`\nFound: `%s`";
- $data = array(
+ $data = [
$expected,
$annotation,
- );
+ ];
$fix = $phpcsFile->addFixableError( $error, $stackPtr, $errorCode, $data );
if ( $fix === true ) {
diff --git a/Yoast/Sniffs/Commenting/TestsHaveCoversTagSniff.php b/Yoast/Sniffs/Commenting/TestsHaveCoversTagSniff.php
index 4f002d9a..c8ec5527 100644
--- a/Yoast/Sniffs/Commenting/TestsHaveCoversTagSniff.php
+++ b/Yoast/Sniffs/Commenting/TestsHaveCoversTagSniff.php
@@ -22,10 +22,10 @@ class TestsHaveCoversTagSniff implements Sniff {
* @return array
*/
public function register() {
- return array(
+ return [
T_CLASS,
T_FUNCTION,
- );
+ ];
}
/**
@@ -60,7 +60,7 @@ public function process( File $phpcsFile, $stackPtr ) {
* in the stack passed in $tokens.
*
* @return void|int If covers annotations were found (or this is not a test class),
- * will returns the stack pointer to the end of the class.
+ * will return the stack pointer to the end of the class.
*/
protected function process_class( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
@@ -182,7 +182,7 @@ protected function process_function( File $phpcsFile, $stackPtr ) {
'Each test function should have at least one @covers tag annotating which class/method/function is being tested. Tag missing for function %s()',
$stackPtr,
'Missing',
- array( $name )
+ [ $name ]
);
}
}
diff --git a/Yoast/Sniffs/ControlStructures/IfElseDeclarationSniff.php b/Yoast/Sniffs/ControlStructures/IfElseDeclarationSniff.php
index f9bf9abd..d9e68782 100644
--- a/Yoast/Sniffs/ControlStructures/IfElseDeclarationSniff.php
+++ b/Yoast/Sniffs/ControlStructures/IfElseDeclarationSniff.php
@@ -23,10 +23,10 @@ class IfElseDeclarationSniff implements Sniff {
* @return array
*/
public function register() {
- return array(
+ return [
T_ELSE,
T_ELSEIF,
- );
+ ];
}
/**
@@ -64,7 +64,7 @@ public function process( File $phpcsFile, $stackPtr ) {
'%s statement must be on a new line',
$stackPtr,
'NewLine',
- array( ucfirst( $tokens[ $stackPtr ]['content'] ) )
+ [ ucfirst( $tokens[ $stackPtr ]['content'] ) ]
);
}
elseif ( $tokens[ $previous_scope_closer ]['column'] !== $tokens[ $stackPtr ]['column'] ) {
@@ -72,7 +72,7 @@ public function process( File $phpcsFile, $stackPtr ) {
'%s statement not aligned with previous part of the control structure',
$stackPtr,
'Alignment',
- array( ucfirst( $tokens[ $stackPtr ]['content'] ) )
+ [ ucfirst( $tokens[ $stackPtr ]['content'] ) ]
);
}
@@ -80,10 +80,10 @@ public function process( File $phpcsFile, $stackPtr ) {
if ( $previous_scope_closer !== $previous_non_empty ) {
$error = 'Nothing but whitespace and comments allowed between closing bracket and %s statement, found "%s"';
- $data = array(
+ $data = [
$tokens[ $stackPtr ]['content'],
trim( $phpcsFile->getTokensAsString( ( $previous_scope_closer + 1 ), ( $stackPtr - ( $previous_scope_closer + 1 ) ) ) ),
- );
+ ];
$phpcsFile->addError( $error, $stackPtr, 'StatementFound', $data );
}
}
diff --git a/Yoast/Sniffs/Files/FileNameSniff.php b/Yoast/Sniffs/Files/FileNameSniff.php
index 506fe309..5e499e2d 100644
--- a/Yoast/Sniffs/Files/FileNameSniff.php
+++ b/Yoast/Sniffs/Files/FileNameSniff.php
@@ -7,8 +7,6 @@
use PHP_CodeSniffer\Util\Common;
/**
- * YoastCS\Yoast\Sniffs\Files\FileNameSniff.
- *
* Ensures files comply with the Yoast file name rules.
*
* Rules:
@@ -39,7 +37,7 @@ class FileNameSniff implements Sniff {
*
* @var string[]
*/
- public $prefixes = array();
+ public $oo_prefixes = [];
/**
* List of files to exclude from the strict file name check.
@@ -59,18 +57,18 @@ class FileNameSniff implements Sniff {
*
* @var string[]
*/
- public $exclude = array();
+ public $excluded_files_strict_check = [];
/**
* Object tokens to search for in a file.
*
* @var array
*/
- private $oo_tokens = array(
+ private $oo_tokens = [
T_CLASS,
T_INTERFACE,
T_TRAIT,
- );
+ ];
/**
* Returns an array of tokens this test wants to listen for.
@@ -78,7 +76,7 @@ class FileNameSniff implements Sniff {
* @return array
*/
public function register() {
- return array( T_OPEN_TAG );
+ return [ T_OPEN_TAG ];
}
/**
@@ -128,7 +126,7 @@ public function process( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
$name = $phpcsFile->getDeclarationName( $oo_structure );
- $prefixes = $this->clean_custom_array_property( $this->prefixes );
+ $prefixes = $this->clean_custom_array_property( $this->oo_prefixes );
if ( ! empty( $prefixes ) ) {
// Use reverse natural sorting to get the longest of overlapping prefixes first.
rsort( $prefixes, ( SORT_NATURAL | SORT_FLAG_CASE ) );
@@ -189,10 +187,10 @@ public function process( File $phpcsFile, $stackPtr ) {
$error,
0,
$error_code,
- array(
+ [
$expected . '.' . $extension,
$basename,
- )
+ ]
);
}
@@ -210,10 +208,10 @@ public function process( File $phpcsFile, $stackPtr ) {
* @return bool
*/
protected function is_file_excluded( File $phpcsFile, $path_to_file ) {
- $exclude = $this->clean_custom_array_property( $this->exclude, true, true );
+ $exclude = $this->clean_custom_array_property( $this->excluded_files_strict_check, true, true );
if ( ! empty( $exclude ) ) {
- $exclude = array_map( array( $this, 'normalize_directory_separators' ), $exclude );
+ $exclude = array_map( [ $this, 'normalize_directory_separators' ], $exclude );
$path_to_file = $this->normalize_directory_separators( $path_to_file );
if ( ! isset( $phpcsFile->config->basepath ) ) {
diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php
index e909f39b..33359866 100644
--- a/Yoast/Sniffs/Files/TestDoublesSniff.php
+++ b/Yoast/Sniffs/Files/TestDoublesSniff.php
@@ -33,9 +33,9 @@ class TestDoublesSniff implements Sniff {
*
* @var array
*/
- public $doubles_path = array(
+ public $doubles_path = [
'/tests/doubles',
- );
+ ];
/**
* Validated absolute target paths for test double/mock classes or an empty array
@@ -51,11 +51,11 @@ class TestDoublesSniff implements Sniff {
* @return array
*/
public function register() {
- return array(
+ return [
T_CLASS,
T_INTERFACE,
T_TRAIT,
- );
+ ];
}
/**
@@ -120,7 +120,7 @@ public function process( File $phpcsFile, $stackPtr ) {
$base_path = rtrim( $base_path, '/' ) . '/'; // Make sure the base_path ends in a single slash.
if ( ! isset( $this->target_paths ) || defined( 'PHP_CODESNIFFER_IN_TESTS' ) ) {
- $this->target_paths = array();
+ $this->target_paths = [];
foreach ( $this->doubles_path as $doubles_path ) {
$target_path = $base_path;
@@ -134,9 +134,9 @@ public function process( File $phpcsFile, $stackPtr ) {
if ( empty( $this->target_paths ) ) {
// No valid target paths found.
- $data = array(
+ $data = [
$phpcsFile->config->basepath,
- );
+ ];
if ( count( $this->doubles_path ) === 1 ) {
$data[] = 'directory';
@@ -169,10 +169,10 @@ public function process( File $phpcsFile, $stackPtr ) {
}
if ( $is_error === true ) {
- $data = array(
+ $data = [
$tokens[ $stackPtr ]['content'],
$object_name,
- );
+ ];
$phpcsFile->addError(
'Double/Mock test helper classes should be placed in a dedicated test doubles sub-directory. Found %s: %s',
@@ -189,12 +189,12 @@ public function process( File $phpcsFile, $stackPtr ) {
}
if ( $more_objects_in_file !== false ) {
- $data = array(
+ $data = [
$tokens[ $stackPtr ]['content'],
$object_name,
$tokens[ $more_objects_in_file ]['content'],
$phpcsFile->getDeclarationName( $more_objects_in_file ),
- );
+ ];
$phpcsFile->addError(
'Double/Mock test helper classes should be in their own file. Found %1$s: %2$s and %3$s: %4$s',
diff --git a/Yoast/Sniffs/Namespaces/NamespaceDeclarationSniff.php b/Yoast/Sniffs/Namespaces/NamespaceDeclarationSniff.php
index d69747b2..4fcba833 100644
--- a/Yoast/Sniffs/Namespaces/NamespaceDeclarationSniff.php
+++ b/Yoast/Sniffs/Namespaces/NamespaceDeclarationSniff.php
@@ -30,9 +30,9 @@ class NamespaceDeclarationSniff implements Sniff {
* @return array
*/
public function register() {
- return array(
+ return [
T_OPEN_TAG,
- );
+ ];
}
/**
@@ -48,7 +48,7 @@ public function process( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
- $statements = array();
+ $statements = [];
while ( ( $stackPtr = $phpcsFile->findNext( T_NAMESPACE, ( $stackPtr + 1 ) ) ) !== false ) {
@@ -91,10 +91,10 @@ public function process( File $phpcsFile, $stackPtr ) {
$count = count( $statements );
if ( $count > 1 ) {
- $data = array(
+ $data = [
$count,
$tokens[ $statements[0] ]['line'],
- );
+ ];
for ( $i = 1; $i < $count; $i++ ) {
$phpcsFile->addError(
diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php
new file mode 100644
index 00000000..ae400ffd
--- /dev/null
+++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php
@@ -0,0 +1,358 @@
+filter_allow_only_namespace_prefixes( $prefixes );
+ }
+
+ /**
+ * Processes this test, when one of its tokens is encountered.
+ *
+ * @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 StackPtr to the end of the file, this sniff needs to only
+ * check each file once.
+ */
+ public function process( File $phpcsFile, $stackPtr ) {
+
+ $tokens = $phpcsFile->getTokens();
+
+ if ( empty( $tokens[ $stackPtr ]['conditions'] ) === false ) {
+ // Not a namespace declaration.
+ return;
+ }
+
+ $next_non_empty = $phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true );
+ if ( $tokens[ $next_non_empty ]['code'] === T_NS_SEPARATOR ) {
+ // Not a namespace declaration.
+ return;
+ }
+
+ // Get the complete namespace name.
+ $namespace_name = $tokens[ $next_non_empty ]['content'];
+ for ( $i = ( $next_non_empty + 1 ); ; $i++ ) {
+ if ( isset( Tokens::$emptyTokens[ $tokens[ $i ]['code'] ] ) ) {
+ continue;
+ }
+
+ if ( $tokens[ $i ]['code'] !== T_STRING && $tokens[ $i ]['code'] !== T_NS_SEPARATOR ) {
+ // Reached end of the namespace declaration.
+ break;
+ }
+
+ $namespace_name .= $tokens[ $i ]['content'];
+ }
+
+ $this->validate_prefixes();
+
+ // Strip off the plugin prefix.
+ $namespace_name_no_prefix = $namespace_name;
+ $found_prefix = '';
+ if ( ! empty( $this->validated_prefixes ) ) {
+ $name = $namespace_name . '\\'; // Validated prefixes always have a \ at the end.
+ foreach ( $this->validated_prefixes as $prefix ) {
+ if ( strpos( $name . '\\', $prefix ) === 0 ) {
+ $namespace_name_no_prefix = rtrim( substr( $name, strlen( $prefix ) ), '\\' );
+ $found_prefix = rtrim( $prefix, '\\' );
+ break;
+ }
+ }
+ unset( $prefix, $name );
+ }
+
+ /*
+ * Check the namespace level depth.
+ */
+ if ( $namespace_name_no_prefix !== '' ) {
+ $namespace_for_level_check = $namespace_name_no_prefix;
+ // Allow for `Tests\` and `Tests\Doubles\` after the prefix.
+ if ( strpos( $namespace_for_level_check, 'Tests\\' ) === 0 ) {
+ $namespace_for_level_check = substr( $namespace_for_level_check, 6 );
+ if ( strpos( $namespace_for_level_check, 'Doubles\\' ) === 0 ) {
+ $namespace_for_level_check = substr( $namespace_for_level_check, 8 );
+ }
+ }
+
+ $parts = explode( '\\', $namespace_for_level_check );
+ $part_count = count( $parts );
+
+ if ( $part_count > $this->max_levels ) {
+ $error = 'A namespace name is not allowed to be more than %d levels deep (excluding the prefix). Level depth found: %d in %s';
+ $data = [
+ $this->max_levels,
+ $part_count,
+ $namespace_name,
+ ];
+
+ $phpcsFile->addError( $error, $stackPtr, 'MaxExceeded', $data );
+ }
+ elseif ( $part_count > $this->recommended_max_levels ) {
+ $error = 'A namespace name should be no more than %d levels deep (excluding the prefix). Level depth found: %d in %s';
+ $data = [
+ $this->recommended_max_levels,
+ $part_count,
+ $namespace_name,
+ ];
+
+ $phpcsFile->addWarning( $error, $stackPtr, 'TooLong', $data );
+ }
+ }
+
+ /*
+ * Prepare to check the path to level translation.
+ */
+
+ if ( ! isset( $phpcsFile->config->basepath ) ) {
+ // If no basepath is set, we don't know the project root, so bow out.
+ return;
+ }
+
+ $base_path = $this->normalize_directory_separators( $phpcsFile->config->basepath );
+
+ // Stripping potential quotes to ensure `stdin_path` passed by IDEs does not include quotes.
+ $file = preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $phpcsFile->getFileName() );
+
+ if ( $file === 'STDIN' ) {
+ return;
+ }
+
+ $directory = $this->normalize_directory_separators( dirname( $file ) );
+ $relative_directory = Common::stripBasepath( $directory, $base_path );
+ if ( $relative_directory === '.' ) {
+ $relative_directory = '';
+ }
+ else {
+ if ( $relative_directory[0] !== '/' ) {
+ /*
+ * Basepath stripping appears to work differently depending on OS.
+ * On Windows there still is a slash at the start, on Unix/Mac there isn't.
+ * Normalize to allow comparison.
+ */
+ $relative_directory = '/' . $relative_directory;
+ }
+
+ // Add trailing slash to prevent matching '/sub' to '/sub-directory'.
+ $relative_directory .= '/';
+ }
+
+ $this->validate_src_directory();
+
+ if ( empty( $this->validated_src_directory ) === false ) {
+ foreach ( $this->validated_src_directory as $subdirectory ) {
+ if ( strpos( $relative_directory, $subdirectory ) !== 0 ) {
+ continue;
+ }
+
+ $relative_directory = substr( $relative_directory, strlen( $subdirectory ) );
+ break;
+ }
+ }
+
+ // Now any potential src directory has been stripped, remove the slashes again.
+ $relative_directory = trim( $relative_directory, '/' );
+
+ $namespace_name_for_translation = str_replace(
+ [ '_', '\\' ], // Find.
+ [ '-', '/' ], // Replace with.
+ $namespace_name_no_prefix
+ );
+
+ if ( strcasecmp( $relative_directory, $namespace_name_for_translation ) === 0 ) {
+ return;
+ }
+
+ $expected = '';
+ if ( $found_prefix !== '' ) {
+ $expected = $found_prefix;
+ }
+ else {
+ $expected = '[Plugin\Prefix]';
+ }
+
+ if ( $relative_directory !== '' ) {
+ $levels = explode( '/', $relative_directory );
+ $levels = array_filter( $levels ); // Remove empties.
+ foreach ( $levels as $level ) {
+ $words = explode( '-', $level );
+ $words = array_map( 'ucfirst', $words );
+ $expected .= '\\' . implode( '_', $words );
+ }
+ }
+
+ $phpcsFile->addError(
+ 'The namespace (sub)level(s) should reflect the directory path to the file. Expected: "%s"; Found: "%s"',
+ $stackPtr,
+ 'Invalid',
+ [
+ $expected,
+ $namespace_name,
+ ]
+ );
+ }
+
+ /**
+ * Validate a $src_directory property when set in a custom ruleset.
+ *
+ * @return void
+ */
+ protected function validate_src_directory() {
+ if ( $this->previous_src_directory === $this->src_directory ) {
+ return;
+ }
+
+ // Set the cache *before* validation so as to not break the above compare.
+ $this->previous_src_directory = $this->src_directory;
+
+ $src_directory = (array) $this->src_directory;
+ $src_directory = array_filter( array_map( 'trim', $src_directory ) );
+
+ if ( empty( $src_directory ) ) {
+ $this->validated_src_directory = [];
+ return;
+ }
+
+ $validated = [];
+ foreach ( $src_directory as $directory ) {
+ if ( strpos( $directory, '..' ) !== false ) {
+ // Do not allow walking up the directory hierarchy.
+ continue;
+ }
+
+ $directory = $this->normalize_directory_separators( $directory );
+
+ if ( $directory === '.' ) {
+ // The basepath/root directory is the default, so ignore.
+ continue;
+ }
+
+ if ( strpos( $directory, './' ) === 0 ) {
+ $directory = substr( $directory, 2 );
+ }
+
+ if ( $directory === '' ) {
+ continue;
+ }
+
+ $validated[] = '/' . $directory . '/';
+ }
+
+ // Use reverse natural sorting to get the longest directory first.
+ rsort( $validated, ( SORT_NATURAL | SORT_FLAG_CASE ) );
+
+ // Set the validated prefixes cache.
+ $this->validated_src_directory = $validated;
+ }
+
+ /**
+ * Normalize all directory separators to be a forward slash and remove prefixed and suffixed slashes.
+ *
+ * @param string $path Path to normalize.
+ *
+ * @return string
+ */
+ private function normalize_directory_separators( $path ) {
+ return trim( strtr( $path, '\\', '/' ), '/' );
+ }
+}
diff --git a/Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php b/Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php
new file mode 100644
index 00000000..c20ffa5e
--- /dev/null
+++ b/Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php
@@ -0,0 +1,159 @@
+ true,
+ 'Mock' => false,
+ 'Double' => false,
+ ];
+
+ /**
+ * Returns an array of tokens this test wants to listen for.
+ *
+ * @return array
+ */
+ public function register() {
+ return [
+ T_CLASS,
+ T_INTERFACE,
+ T_TRAIT,
+ ];
+ }
+
+ /**
+ * Processes this test, when one of its tokens is encountered.
+ *
+ * @param int $stackPtr The position of the current token
+ * in the stack passed in $tokens.
+ *
+ * @return void
+ */
+ public function process_token( $stackPtr ) {
+
+ // Check whether we are in a namespace or not.
+ if ( $this->determine_namespace( $stackPtr ) === '' ) {
+ return;
+ }
+
+ $object_name = $this->phpcsFile->getDeclarationName( $stackPtr );
+ if ( empty( $object_name ) ) {
+ return;
+ }
+
+ $parts = explode( '_', $object_name );
+ $part_count = count( $parts );
+
+ /*
+ * Allow the class name to be one part longer for confirmed test/mock/double classes.
+ */
+ $last = array_pop( $parts );
+ if ( isset( $this->test_suffixes[ $last ] ) ) {
+ if ( $this->test_suffixes[ $last ] === true && $this->is_test_class( $stackPtr ) ) {
+ --$part_count;
+ }
+ else {
+ $extends = $this->phpcsFile->findExtendedClassName( $stackPtr );
+ if ( is_string( $extends ) ) {
+ --$part_count;
+ }
+ }
+ }
+
+ if ( $part_count <= $this->recommended_max_words && $part_count <= $this->max_words ) {
+ return;
+ }
+
+ // Check if the class is deprecated.
+ $find = [
+ T_ABSTRACT => T_ABSTRACT,
+ T_FINAL => T_FINAL,
+ T_WHITESPACE => T_WHITESPACE,
+ ];
+
+ $comment_end = $this->phpcsFile->findPrevious( $find, ( $stackPtr - 1 ), null, true );
+ if ( $this->tokens[ $comment_end ]['code'] === T_DOC_COMMENT_CLOSE_TAG ) {
+ // Only check if the class has a docblock.
+ $comment_start = $this->tokens[ $comment_end ]['comment_opener'];
+ foreach ( $this->tokens[ $comment_start ]['comment_tags'] as $tag ) {
+ if ( $this->tokens[ $tag ]['content'] === '@deprecated' ) {
+ // Deprecated class, ignore.
+ return;
+ }
+ }
+ }
+
+ // Active class.
+ $object_type = 'a ' . $this->tokens[ $stackPtr ]['content'];
+ if ( $this->tokens[ $stackPtr ]['code'] === \T_INTERFACE ) {
+ $object_type = 'an ' . $this->tokens[ $stackPtr ]['content'];
+ }
+
+ if ( $part_count > $this->max_words ) {
+ $error = 'The name of %s is not allowed to consist of more than %d words. Words found: %d in %s';
+ $data = [
+ $object_type,
+ $this->max_words,
+ $part_count,
+ $object_name,
+ ];
+
+ $this->phpcsFile->addError( $error, $stackPtr, 'MaxExceeded', $data );
+ }
+ elseif ( $part_count > $this->recommended_max_words ) {
+ $error = 'The name of %s should not consist of more than %d words. Words found: %d in %s';
+ $data = [
+ $object_type,
+ $this->recommended_max_words,
+ $part_count,
+ $object_name,
+ ];
+
+ $this->phpcsFile->addWarning( $error, $stackPtr, 'TooLong', $data );
+ }
+ }
+}
diff --git a/Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php b/Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php
new file mode 100644
index 00000000..13c5a7a4
--- /dev/null
+++ b/Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php
@@ -0,0 +1,317 @@
+remove_prefix = true;
+ $this->found_prefix = '';
+ $this->first_string = '';
+ $this->validate_prefixes();
+
+ /*
+ * If any prefixes were passed, check if this is a hook belonging to the plugin being checked.
+ */
+ if ( empty( $this->validated_prefixes ) === false ) {
+ $param = $parameters[1];
+ $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param['start'], ( $param['end'] + 1 ), true );
+ $found_prefix = '';
+
+ if ( isset( Tokens::$stringTokens[ $this->tokens[ $first_non_empty ]['code'] ] ) ) {
+ $content = trim( $this->strip_quotes( $this->tokens[ $first_non_empty ]['content'] ) );
+ foreach ( $this->validated_prefixes as $prefix ) {
+ if ( strpos( $content, $prefix ) === 0 ) {
+ $found_prefix = $prefix;
+ break;
+ }
+ }
+ }
+
+ if ( $found_prefix === '' ) {
+ /*
+ * Not a hook name with a prefix indicating it belongs to the specific plugin
+ * being checked. Ignore as it's probably a WP Core or external plugin hook name
+ * which we cannot change.
+ */
+ return;
+ }
+ }
+
+ // Do the WPCS native hook name check.
+ parent::process_parameters( $stackPtr, $group_name, $matched_content, $parameters );
+
+ if ( $this->found_prefix === '' ) {
+ return;
+ }
+
+ // Do the YoastCS specific hook name length and prefix check.
+ $this->verify_yoast_hook_name( $stackPtr, $parameters );
+ }
+
+ /**
+ * Transform an arbitrary string to lowercase and replace punctuation and spaces with underscores.
+ *
+ * This overloads the parent to prevent errors being triggered on the Yoast specific
+ * plugin prefix for hook names and remembers whether a prefix was found to allow
+ * checking whether it was the correct one.
+ *
+ * @param string $string The target string.
+ * @param string $regex The punctuation regular expression to use.
+ * @param string $transform_type Whether to a partial or complete transform.
+ * Valid values are: 'full', 'case', 'punctuation'.
+ * @return string
+ */
+ protected function transform( $string, $regex, $transform_type = 'full' ) {
+
+ if ( empty( $this->validated_prefixes ) ) {
+ $this->remove_prefix = false;
+ return parent::transform( $string, $regex, $transform_type );
+ }
+
+ // Not the first text string.
+ if ( $this->remove_prefix === false
+ && $string !== $this->first_string
+ ) {
+ return parent::transform( $string, $regex, $transform_type );
+ }
+
+ // Repeated call for the first text string.
+ if ( $this->remove_prefix === false
+ && $string === $this->first_string
+ ) {
+ if ( $this->found_prefix !== '' ) {
+ $string = substr( $string, strlen( $this->found_prefix ) );
+ }
+
+ return $this->found_prefix . parent::transform( $string, $regex, $transform_type );
+ }
+
+ // First call for first text string.
+ if ( $this->remove_prefix === true ) {
+ $this->first_string = $string;
+
+ foreach ( $this->validated_prefixes as $prefix ) {
+ if ( strpos( $string, $prefix ) === 0 ) {
+ $string = substr( $string, strlen( $prefix ) );
+ $this->found_prefix = $prefix;
+
+ /*
+ * Handle case where the prefix is the only content in a single quoted string,
+ * which would necessitate an extra backslash to escape the end backslash.
+ * I.e. 'Yoast\WP\Plugin\\'.
+ */
+ if ( $string === '\\' ) {
+ $string = '';
+ $this->found_prefix .= '\\';
+ }
+
+ break;
+ }
+ }
+
+ // Don't do this again until the next time the sniff gets triggered.
+ $this->remove_prefix = false;
+ }
+
+ return $this->found_prefix . parent::transform( $string, $regex, $transform_type );
+ }
+
+ /**
+ * Additional YoastCS specific hook name checks.
+ *
+ * @param int $stackPtr The position of the current token in the stack.
+ * @param array $parameters Array with information about the parameters.
+ *
+ * @return void
+ */
+ public function verify_yoast_hook_name( $stackPtr, $parameters ) {
+
+ $param = $parameters[1];
+ $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param['start'], ( $param['end'] + 1 ), true );
+
+ /*
+ * Check that the namespace-like prefix is used for hooks.
+ */
+ if ( strpos( $this->found_prefix, '\\' ) === false ) {
+ /*
+ * Find which namespace-based prefix should have been used.
+ * Loop till the end as the shortest prefix will be last.
+ */
+ $namespace_prefix = '';
+ foreach ( $this->validated_prefixes as $prefix ) {
+ if ( strpos( $prefix, '\\' ) !== false ) {
+ $namespace_prefix = $prefix;
+ }
+ }
+
+ $this->phpcsFile->addWarning(
+ 'Wrong prefix type used. Hook names should use the "%s" namespace-like prefix. Found prefix: %s',
+ $first_non_empty,
+ 'WrongPrefix',
+ [
+ $namespace_prefix,
+ $this->found_prefix,
+ ]
+ );
+ }
+
+ /*
+ * Check if the hook name is a single quoted string.
+ */
+ $allow = [ \T_CONSTANT_ENCAPSED_STRING ];
+ $allow += Tokens::$emptyTokens;
+
+ $has_non_string = $this->phpcsFile->findNext( $allow, $param['start'], ( $param['end'] + 1 ), true );
+ if ( $has_non_string !== false ) {
+ /*
+ * Double quoted string or a hook name concatenated together, checking the word count for the
+ * hook name can not be done in a reliable manner.
+ *
+ * Throwing a warning to allow for examining these if desired.
+ * Severity 3 makes sure that this warning will normally be invisible and will only
+ * be thrown when PHPCS is explicitly requested to check with a lower severity.
+ */
+ $this->phpcsFile->addWarning(
+ 'Hook name could not reliably be examined for maximum word count. Please verify this hook name manually. Found: %s',
+ $first_non_empty,
+ 'NonString',
+ [ $param['raw'] ],
+ 3
+ );
+
+ return;
+ }
+
+ /*
+ * Check the hook name depth.
+ */
+ $hook_ptr = $first_non_empty; // If no other tokens were found, the first non empty will be the hook name.
+ $hook_name = $this->strip_quotes( $this->tokens[ $hook_ptr ]['content'] );
+ $hook_name = substr( $hook_name, strlen( $this->found_prefix ) );
+
+ $parts = explode( '_', $hook_name );
+ $part_count = count( $parts );
+
+ if ( $part_count <= $this->recommended_max_words && $part_count <= $this->max_words ) {
+ return;
+ }
+
+ if ( $part_count > $this->max_words ) {
+ $error = 'A hook name is not allowed to consist of more than %d words after the plugin prefix. Words found: %d in %s';
+ $data = [
+ $this->max_words,
+ $part_count,
+ $this->tokens[ $hook_ptr ]['content'],
+ ];
+
+ $this->phpcsFile->addError( $error, $hook_ptr, 'MaxExceeded', $data );
+ }
+ elseif ( $part_count > $this->recommended_max_words ) {
+ $error = 'A hook name should not consist of more than %d words after the plugin prefix. Words found: %d in %s';
+ $data = [
+ $this->recommended_max_words,
+ $part_count,
+ $this->tokens[ $hook_ptr ]['content'],
+ ];
+
+ $this->phpcsFile->addWarning( $error, $hook_ptr, 'TooLong', $data );
+ }
+ }
+}
diff --git a/Yoast/Sniffs/Yoast/AlternativeFunctionsSniff.php b/Yoast/Sniffs/Yoast/AlternativeFunctionsSniff.php
index 2e595151..639aafab 100644
--- a/Yoast/Sniffs/Yoast/AlternativeFunctionsSniff.php
+++ b/Yoast/Sniffs/Yoast/AlternativeFunctionsSniff.php
@@ -20,17 +20,17 @@ class AlternativeFunctionsSniff extends AbstractFunctionRestrictionsSniff {
* @return array
*/
public function getGroups() {
- return array(
- 'json_encode' => array(
+ return [
+ 'json_encode' => [
'type' => 'error',
'message' => 'Detected a call to %s(). Use %s() instead.',
- 'functions' => array(
+ 'functions' => [
'json_encode',
'wp_json_encode',
- ),
+ ],
'replacement' => 'WPSEO_Utils::format_json_encode',
- ),
- );
+ ],
+ ];
}
/**
@@ -54,10 +54,10 @@ public function process_matched_token( $stackPtr, $group_name, $matched_content
$message = $this->groups[ $group_name ]['message'];
$is_error = ( $this->groups[ $group_name ]['type'] === 'error' );
$error_code = $this->string_to_errorcode( $group_name . '_' . $matched_content );
- $data = array(
+ $data = [
$matched_content,
$replacement,
- );
+ ];
/*
* Deal with specific situations.
diff --git a/Yoast/Tests/Commenting/CodeCoverageIgnoreDeprecatedUnitTest.php b/Yoast/Tests/Commenting/CodeCoverageIgnoreDeprecatedUnitTest.php
index b935d4a8..e28989dc 100644
--- a/Yoast/Tests/Commenting/CodeCoverageIgnoreDeprecatedUnitTest.php
+++ b/Yoast/Tests/Commenting/CodeCoverageIgnoreDeprecatedUnitTest.php
@@ -21,11 +21,11 @@ class CodeCoverageIgnoreDeprecatedUnitTest extends AbstractSniffUnitTest {
* @return array =>
*/
public function getErrorList() {
- return array(
+ return [
41 => 1,
50 => 1,
55 => 1,
- );
+ ];
}
/**
@@ -34,6 +34,6 @@ public function getErrorList() {
* @return array =>
*/
public function getWarningList() {
- return array();
+ return [];
}
}
diff --git a/Yoast/Tests/Commenting/CoversTagUnitTest.php b/Yoast/Tests/Commenting/CoversTagUnitTest.php
index 9734933f..b4a2c1b1 100644
--- a/Yoast/Tests/Commenting/CoversTagUnitTest.php
+++ b/Yoast/Tests/Commenting/CoversTagUnitTest.php
@@ -21,7 +21,7 @@ class CoversTagUnitTest extends AbstractSniffUnitTest {
* @return array =>
*/
public function getErrorList() {
- return array(
+ return [
34 => 1,
35 => 1,
36 => 1,
@@ -50,7 +50,7 @@ public function getErrorList() {
140 => 1,
150 => 1,
151 => 1,
- );
+ ];
}
/**
@@ -59,6 +59,6 @@ public function getErrorList() {
* @return array =>
*/
public function getWarningList() {
- return array();
+ return [];
}
}
diff --git a/Yoast/Tests/Commenting/FileCommentUnitTest.php b/Yoast/Tests/Commenting/FileCommentUnitTest.php
index 7fc68063..eaaaebad 100644
--- a/Yoast/Tests/Commenting/FileCommentUnitTest.php
+++ b/Yoast/Tests/Commenting/FileCommentUnitTest.php
@@ -27,12 +27,12 @@ public function getErrorList( $testFile = '' ) {
case 'FileCommentUnitTest.2.inc':
case 'FileCommentUnitTest.8.inc':
case 'FileCommentUnitTest.10.inc':
- return array(
+ return [
1 => 1,
- );
+ ];
default:
- return array();
+ return [];
}
}
@@ -47,12 +47,12 @@ public function getWarningList( $testFile = '' ) {
switch ( $testFile ) {
case 'FileCommentUnitTest.4.inc':
case 'FileCommentUnitTest.6.inc':
- return array(
+ return [
2 => 1,
- );
+ ];
default:
- return array();
+ return [];
}
}
}
diff --git a/Yoast/Tests/Commenting/TestsHaveCoversTagUnitTest.php b/Yoast/Tests/Commenting/TestsHaveCoversTagUnitTest.php
index 93a39312..201f9b65 100644
--- a/Yoast/Tests/Commenting/TestsHaveCoversTagUnitTest.php
+++ b/Yoast/Tests/Commenting/TestsHaveCoversTagUnitTest.php
@@ -21,10 +21,10 @@ class TestsHaveCoversTagUnitTest extends AbstractSniffUnitTest {
* @return array =>
*/
public function getErrorList() {
- return array(
+ return [
59 => 1,
88 => 1,
- );
+ ];
}
/**
@@ -33,6 +33,6 @@ public function getErrorList() {
* @return array =>
*/
public function getWarningList() {
- return array();
+ return [];
}
}
diff --git a/Yoast/Tests/ControlStructures/IfElseDeclarationUnitTest.php b/Yoast/Tests/ControlStructures/IfElseDeclarationUnitTest.php
index 327794b6..3cf2ba68 100644
--- a/Yoast/Tests/ControlStructures/IfElseDeclarationUnitTest.php
+++ b/Yoast/Tests/ControlStructures/IfElseDeclarationUnitTest.php
@@ -22,7 +22,7 @@ class IfElseDeclarationUnitTest extends AbstractSniffUnitTest {
* @return array =>
*/
public function getErrorList() {
- return array(
+ return [
22 => 1,
28 => 1,
30 => 1,
@@ -32,7 +32,7 @@ public function getErrorList() {
51 => 1,
76 => 1,
84 => 2,
- );
+ ];
}
/**
@@ -41,6 +41,6 @@ public function getErrorList() {
* @return array =>
*/
public function getWarningList() {
- return array();
+ return [];
}
}
diff --git a/Yoast/Tests/Files/FileNameUnitTest.php b/Yoast/Tests/Files/FileNameUnitTest.php
index 52c37108..b76ad069 100644
--- a/Yoast/Tests/Files/FileNameUnitTest.php
+++ b/Yoast/Tests/Files/FileNameUnitTest.php
@@ -20,7 +20,7 @@ class FileNameUnitTest extends AbstractSniffUnitTest {
*
* @var array
*/
- private $expected_results = array(
+ private $expected_results = [
/*
* In /FileNameUnitTests.
@@ -74,7 +74,7 @@ class FileNameUnitTest extends AbstractSniffUnitTest {
// Fall-back file in case glob() fails.
'FileNameUnitTest.inc' => 1,
- );
+ ];
/**
* Set CLI values before the file is tested.
@@ -107,7 +107,7 @@ protected function getTestFiles( $testFileBase ) {
return $test_files;
}
- return array( $testFileBase . '.inc' );
+ return [ $testFileBase . '.inc' ];
}
/**
@@ -120,12 +120,12 @@ protected function getTestFiles( $testFileBase ) {
public function getErrorList( $testFile = '' ) {
if ( isset( $this->expected_results[ $testFile ] ) ) {
- return array(
+ return [
1 => $this->expected_results[ $testFile ],
- );
+ ];
}
- return array();
+ return [];
}
/**
@@ -137,11 +137,11 @@ public function getErrorList( $testFile = '' ) {
*/
public function getWarningList( $testFile = '' ) {
if ( $testFile === 'no-basepath.inc' ) {
- return array(
+ return [
1 => 1,
- );
+ ];
}
- return array();
+ return [];
}
}
diff --git a/Yoast/Tests/Files/FileNameUnitTests/ExcludedFile.inc b/Yoast/Tests/Files/FileNameUnitTests/ExcludedFile.inc
index ad132138..21ac4aaa 100644
--- a/Yoast/Tests/Files/FileNameUnitTests/ExcludedFile.inc
+++ b/Yoast/Tests/Files/FileNameUnitTests/ExcludedFile.inc
@@ -1,6 +1,6 @@
-phpcs:set Yoast.Files.FileName exclude[] ExcludedFile.inc
+phpcs:set Yoast.Files.FileName excluded_files_strict_check[] ExcludedFile.inc
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName exclude[] classes/excluded-CLASS-file.inc
+phpcs:set Yoast.Files.FileName excluded_files_strict_check[] classes/excluded-CLASS-file.inc
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast,yoast-plugin
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast,yoast-plugin
-phpcs:set Yoast.Files.FileName exclude[] excluded-file.inc
+phpcs:set Yoast.Files.FileName excluded_files_strict_check[] excluded-file.inc
-phpcs:set Yoast.Files.FileName exclude[] excluded_file.inc
+phpcs:set Yoast.Files.FileName excluded_files_strict_check[] excluded_file.inc
-phpcs:set Yoast.Files.FileName exclude[] functions/excluded-functions-file.inc
+phpcs:set Yoast.Files.FileName excluded_files_strict_check[] functions/excluded-functions-file.inc
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName exclude[] interfaces/excluded-interface-file.inc
+phpcs:set Yoast.Files.FileName excluded_files_strict_check[] interfaces/excluded-interface-file.inc
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName exclude[] no-basepath.inc
+phpcs:set Yoast.Files.FileName excluded_files_strict_check[] no-basepath.inc
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName exclude[] traits/excluded-trait-file.inc
+phpcs:set Yoast.Files.FileName excluded_files_strict_check[] traits/excluded-trait-file.inc
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
-phpcs:set Yoast.Files.FileName prefixes[] wpseo,yoast
+phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast
1,
- );
+ ];
case 'multiple-objects-in-file.inc':
- return array(
+ return [
5 => 2,
- );
+ ];
case 'multiple-objects-in-file-reverse.inc':
- return array(
+ return [
7 => 2,
- );
+ ];
case 'non-existant-doubles-dir.inc':
- return array(
+ return [
4 => 1,
- );
+ ];
case 'not-in-correct-custom-dir.inc':
- return array(
+ return [
4 => 1,
- );
+ ];
case 'not-in-correct-dir-double.inc':
- return array(
+ return [
3 => 1,
- );
+ ];
case 'not-in-correct-dir-mock.inc':
- return array(
+ return [
3 => 1,
- );
+ ];
// In tests/doubles.
case 'multiple-mocks-in-file.inc':
- return array(
+ return [
3 => 1,
5 => 1,
- );
+ ];
// In tests/doubles-not-correct.
case 'not-in-correct-subdir.inc':
- return array(
+ return [
3 => 1,
- );
+ ];
case 'not-double-or-mock.inc': // In tests.
case 'correct-dir-double.inc': // In tests/doubles.
case 'correct-dir-mock.inc': // In tests/doubles.
case 'correct-custom-dir.inc': // In tests/mocks.
default:
- return array();
+ return [];
}
}
@@ -127,17 +127,17 @@ public function getErrorList( $testFile = '' ) {
public function getWarningList( $testFile = '' ) {
switch ( $testFile ) {
case 'no-basepath.inc':
- return array(
+ return [
1 => 1,
- );
+ ];
case 'no-doubles-path-property.inc':
- return array(
+ return [
1 => 1,
- );
+ ];
default:
- return array();
+ return [];
}
}
}
diff --git a/Yoast/Tests/Namespaces/NamespaceDeclarationUnitTest.php b/Yoast/Tests/Namespaces/NamespaceDeclarationUnitTest.php
index a6572f14..59fa79e8 100644
--- a/Yoast/Tests/Namespaces/NamespaceDeclarationUnitTest.php
+++ b/Yoast/Tests/Namespaces/NamespaceDeclarationUnitTest.php
@@ -25,16 +25,16 @@ class NamespaceDeclarationUnitTest extends AbstractSniffUnitTest {
public function getErrorList( $testFile = '' ) {
switch ( $testFile ) {
case 'NamespaceDeclarationUnitTest.2.inc':
- return array(
+ return [
3 => 1,
5 => 3,
7 => 2,
9 => 3,
11 => 2,
- );
+ ];
default:
- return array();
+ return [];
}
}
@@ -44,6 +44,6 @@ public function getErrorList( $testFile = '' ) {
* @return array =>
*/
public function getWarningList() {
- return array();
+ return [];
}
}
diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.inc
new file mode 100644
index 00000000..b3d9bbc7
--- /dev/null
+++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.inc
@@ -0,0 +1 @@
+basepath = __DIR__ . DIRECTORY_SEPARATOR . 'NamespaceNameUnitTests';
+ }
+
+ /**
+ * Get a list of all test files to check.
+ *
+ * @param string $testFileBase The base path that the unit tests files will have.
+ *
+ * @return string[]
+ */
+ protected function getTestFiles( $testFileBase ) {
+ $sep = DIRECTORY_SEPARATOR;
+ $test_files = glob( dirname( $testFileBase ) . $sep . 'NamespaceNameUnitTests{' . $sep . ',' . $sep . '*' . $sep . '}*.inc', GLOB_BRACE );
+
+ if ( ! empty( $test_files ) ) {
+ return $test_files;
+ }
+
+ return [ $testFileBase . '.inc' ];
+ }
+
+ /**
+ * Returns the lines where errors should occur.
+ *
+ * @param string $testFile The name of the file being tested.
+ *
+ * @return array =>
+ */
+ public function getErrorList( $testFile = '' ) {
+
+ switch ( $testFile ) {
+ // Level check tests.
+ case 'no-basepath.inc':
+ return [
+ 12 => 1,
+ 21 => 1,
+ 24 => 1,
+ 33 => 1,
+ 44 => 1,
+ 53 => 1,
+ 54 => 1,
+ 66 => 1,
+ 70 => 1,
+ 74 => 1,
+ ];
+
+ case 'no-basepath-scoped.inc':
+ return [
+ 14 => 1,
+ 24 => 1,
+ ];
+
+ // Basic path translation tests.
+ case 'path-translation-root.inc':
+ return [
+ 11 => 1,
+ 14 => 1,
+ ];
+
+ case 'path-translation-sub1.inc':
+ return [
+ 11 => 1,
+ 12 => 1,
+ 13 => 1,
+ ];
+
+ case 'path-translation-sub2.inc':
+ return [
+ 12 => 1,
+ 13 => 1,
+ 14 => 1,
+ 15 => 1,
+ ];
+
+ // Path translation with $src_directory set tests.
+ case 'path-translation-src.inc':
+ return [
+ 12 => 1,
+ ];
+
+ case 'path-translation-src-sub-a.inc':
+ return [
+ 13 => 1,
+ ];
+
+ case 'path-translation-src-sub-b.inc':
+ return [
+ 14 => 1,
+ ];
+
+ // Path translation with multiple items in $src_directory tests.
+ case 'path-translation-secondary.inc':
+ return [
+ 13 => 1,
+ ];
+
+ case 'path-translation-secondary-sub-a.inc':
+ return [
+ 12 => 1,
+ ];
+
+ // Path translation with multi-level item in $src_directory tests.
+ case 'path-translation-ignore-src.inc':
+ return [
+ 12 => 1,
+ ];
+
+ case 'path-translation-ignore-src-sub-path.inc':
+ return [
+ 12 => 1,
+ 13 => 1,
+ 14 => 1,
+ ];
+
+ // Path translation with no matching $src_directory.
+ case 'path-translation-mismatch.inc':
+ return [
+ 13 => 1,
+ ];
+
+ case 'path-translation-mismatch-illegal.inc':
+ return [
+ 12 => 1,
+ ];
+
+ default:
+ return [];
+ }
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @param string $testFile The name of the file being tested.
+ *
+ * @return array =>
+ */
+ public function getWarningList( $testFile = '' ) {
+ switch ( $testFile ) {
+ // Level check tests.
+ case 'no-basepath.inc':
+ return [
+ 8 => 1,
+ 20 => 1,
+ 23 => 1,
+ 32 => 1,
+ 43 => 1,
+ 65 => 1,
+ 69 => 1,
+ 72 => 1,
+ 73 => 1,
+ ];
+
+ case 'no-basepath-scoped.inc':
+ return [
+ 13 => 1,
+ ];
+
+ case 'path-translation-ignore-src-sub-path.inc':
+ return [
+ 14 => 1,
+ ];
+
+ default:
+ return [];
+ }
+ }
+}
diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/ignore/src/path-translation-ignore-src.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/ignore/src/path-translation-ignore-src.inc
new file mode 100644
index 00000000..6c7a78cc
--- /dev/null
+++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/ignore/src/path-translation-ignore-src.inc
@@ -0,0 +1,16 @@
+ max).
+ */
+// phpcs:set Yoast.NamingConventions.NamespaceName max_levels 2
+// phpcs:set Yoast.NamingConventions.NamespaceName recommended_max_levels 5
+
+namespace Yoast\WP\Plugin\Foo\Bar; // OK.
+namespace Yoast\WP\Plugin\Foo\Bar\Baz; // Error.
+namespace Yoast\WP\Plugin\Foo\Bar\Baz\Fro; // Error.
+
+// Reset to default settings.
+// phpcs:set Yoast.NamingConventions.NamespaceName max_levels 3
+// phpcs:set Yoast.NamingConventions.NamespaceName recommended_max_levels 2
+
+/*
+ * Test allowance for `Tests\` and `Tests\Doubles\` just after the prefix.
+ */
+
+namespace Yoast\WP\Plugin\Tests\Foo\Bar; // OK.
+namespace Yoast\WP\Plugin\Tests\Foo\Bar\Baz; // Warning.
+namespace Yoast\WP\Plugin\Tests\Foo\Bar\Baz\Fro; // Error.
+
+namespace Yoast\WP\Plugin\Tests\Doubles\Foo\Bar; // OK.
+namespace Yoast\WP\Plugin\Tests\Doubles\Foo\Bar\Baz; // Warning.
+namespace Yoast\WP\Plugin\Tests\Doubles\Foo\Bar\Baz\Fro; // Error.
+
+namespace Yoast\WP\Plugin\Foo\Tests\Bar; // Warning, `Tests\` counted as not directly after the prefix.
+namespace Yoast\WP\Plugin\Tests\Foo\Doubles\Bar; // Warning, `Doubles\` counted as not directly after the prefix + `Tests\`.
+namespace Yoast\WP\Plugin\Doubles\Foo\Bar\Fro; // Error, `Doubles\` counted as not found in combination with `Tests\`.
+
+// Reset to default settings.
+// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[]
+
+/*
+ * Test against false positives for namespace operator and incorrect namespace declarations.
+ */
+
+if ( $condition ) {
+ namespace Foo\Bar\Baz\Fro; // Ignore. Parse error.
+}
+
+echo namespace\function_call(); // Ignore. Operator, not keyword.
diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path-translation-root.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path-translation-root.inc
new file mode 100644
index 00000000..cd40436c
--- /dev/null
+++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path-translation-root.inc
@@ -0,0 +1,17 @@
+ max).
+ */
+
+// phpcs:set Yoast.NamingConventions.ObjectNameDepth max_words 2
+// phpcs:set Yoast.NamingConventions.ObjectNameDepth recommended_max_words 5
+
+class Three_Part_ClassName {} // Error.
+class Seven_Part_Class_Name_Too_Long_Too {} // Error.
+
+// Reset to default settings.
+// phpcs:set Yoast.NamingConventions.ObjectNameDepth max_words 3
+// phpcs:set Yoast.NamingConventions.ObjectNameDepth recommended_max_words 3
+
+/*
+ * Ignore deprecated objects.
+ */
+
+/**
+ * Class description, no @deprecated tag.
+ *
+ * @since x.x.x
+ */
+class Active_Class_With_Too_Long_Class_Name {} // Error.
+
+/**
+ * Class description.
+ *
+ * @deprecated x.x.x
+ */
+class Deprecated_Class_With_Too_Long_Class_Name {} // OK.
+
+/*
+ * Allow for a `_Test` suffix in classes within the unit test suite.
+ */
+class Three_Word_Name_Test {} // Error.
+
+class Three_Word_Name_Test extends TestCase {} // OK.
+class Three_Word_Name_Test extends WP_UnitTestCase {} // OK.
+
+class Four_Word_Long_Name_Test extends TestCase {} // Error.
+
+// Similarly for Double/Mock classes.
+class Three_Word_Name_Double {} // Error.
+class Three_Word_Name_Mock {} // Error.
+
+class Three_Word_Name_Double extends Three_Word_Name {} // OK.
+class Three_Word_Name_Mock extends Some_Class {} // OK.
diff --git a/Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php b/Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php
new file mode 100644
index 00000000..ebb91041
--- /dev/null
+++ b/Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php
@@ -0,0 +1,67 @@
+ =>
+ */
+ public function getErrorList( $testFile = '' ) {
+
+ switch ( $testFile ) {
+ case 'ObjectNameDepthUnitTest.2.inc':
+ return [
+ 21 => 1,
+ 22 => 1,
+ 23 => 1,
+ 33 => 1,
+ 42 => 1,
+ 43 => 1,
+ 58 => 1,
+ 70 => 1,
+ 75 => 1,
+ 78 => 1,
+ 79 => 1,
+ ];
+
+ default:
+ return [];
+ }
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @param string $testFile The name of the file being tested.
+ *
+ * @return array =>
+ */
+ public function getWarningList( $testFile = '' ) {
+ switch ( $testFile ) {
+ case 'ObjectNameDepthUnitTest.2.inc':
+ return [
+ 32 => 1,
+ ];
+
+ default:
+ return [];
+ }
+ }
+}
+
diff --git a/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.inc b/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.inc
new file mode 100644
index 00000000..c5d0e34e
--- /dev/null
+++ b/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.inc
@@ -0,0 +1,124 @@
+ID}_Action" ); // Error - use lowercase.
+do_action( "admin_Head_{$obj->Values[3]->name}-Action_{$obj->Values[3]->name}_Action" ); // Error - use lowercase + warning about dash.
+
+/*
+ * Verify the Yoast specific changes to allow for the plugin prefix, while still checking
+ * that the rest of the hook name is lowercase and underscore separated.
+ *
+ * @phpcs:disable Yoast.NamingConventions.ValidHookName.WrongPrefix
+ */
+
+// phpcs:set Yoast.NamingConventions.ValidHookName prefixes[] Yoast\WP\Plugin,yoast_plugin
+
+apply_filters_ref_array( 'yoast_plugin_some_hook_name', $var ); // OK.
+apply_filters( 'Yoast\WP\Plugin\some_hook_name', $var ); // OK.
+
+do_action_ref_array( 'Yoast\WP\Plugin\some-hook\name', $var ); // Warning - disallowed word separators (after prefix).
+apply_filters( 'Yoast\WP\Plugin\some_HookName', $var ); // Error - uppercase chars (after prefix).
+apply_filters_ref_array( 'yoast_plugin_Some-Hook-Name', $var ); // Error + warning, uppercase chars + dashes (after prefix).
+
+// OK, ignored. Hooks do not start with correct prefix, while valid prefixes have been passed.
+do_action( 'Yoast\WP\AnotherPlugin\some_hook_name', $var );
+apply_filters( 'some_hook_name_yoast_plugin_Prefix_not-at_start', $var );
+
+// Second use of the prefix (correctly) not taken into account as not found at the start of the hook name.
+do_action( 'Yoast\WP\Plugin\hook_' . $type . 'Yoast\WP\Plugin\hook' ); // Error + warning x 2, prefix repeated, compound name.
+
+// Test handing of compound hook names with the prefix as stand-alone string.
+apply_filters( 'Yoast\WP\Plugin\\' . $type . '_' . $sub, $var ); // Warning at severity 3 compound name.
+
+// Test handling of escaped slash in double quotes string.
+$var = apply_filters( "Yoast\WP\Plugin\\{$obj->prop['type']}_details", $var ); // Warning at severity 3 compound name.
+
+// phpcs:enable
+
+/*
+ * Test Yoast specific sniff additions for checking hook name length and such.
+ *
+ * These checks are only in effect if a prefix is found.
+ * The WPCS PrefixAllGlobals sniff checks that a prefix is used, that's outside the scope of this sniff.
+ */
+
+/*
+ * Simple strings, no prefix. Includes testing handling of comments.
+ */
+do_action(
+ // phpcs:ignore Stnd.Cat.Sniff -- For reasons.
+ 'some_hook_name_too_long' /* comment */
+); // OK. Plugin prefix not found, so ignored.
+
+/*
+ * Non simple strings.
+ */
+// phpcs:set Yoast.NamingConventions.ValidHookName prefixes[] Yoast\WP\Plugin,yoast_plugin
+
+do_action( "Yoast\WP\Plugin\some_{$variable}_hook_name"); // Warning at severity 3.
+do_action( 'yoast_plugin_some_' . 'hook_' . 'name' ); // Warning at severity 3 + warning wrong prefix.
+
+/*
+ * Test passing "unclean" prefixes property.
+ */
+// phpcs:set Yoast.NamingConventions.ValidHookName prefixes[] \Yoast\WP\Plugin\,_yoast_plugin_
+
+apply_filters( 'Yoast\WP\Plugin\some_hook_name', $var ); // OK.
+apply_filters_ref_array( 'yoast_plugin_some_hook_name', $var ); // Warning - wrong prefix.
+
+apply_filters( 'Yoast\WP\Plugin\some_hook_name_too_long', $var ); // Error - too long.
+do_action_ref_array( "yoast_plugin_some_hook_name_too_long", $var ); // Error - too long + warning - wrong prefix.
+
+/*
+ * Testing with clean prefixes.
+ *
+ * Passing both old-style and new-style prefixes during the transition period.
+ */
+// phpcs:set Yoast.NamingConventions.ValidHookName prefixes[] Yoast\WP\Plugin,Yoast\WP\Plugin\Test,yoast_plugin
+
+do_action( 'Yoast\WP\Plugin\Test\some_hook_name', $var ); // OK.
+do_action_ref_array( "yoast_plugin_some_hook_name", $var ); // Warning - wrong prefix.
+
+apply_filters( 'Yoast\WP\Plugin\some_hook_name_too_long', $var ); // Error - too long.
+do_action_ref_array( "yoast_plugin_some_hook_name_too_long", $var ); // Error - too long + warning - wrong prefix.
+
+/*
+ * Custom word maximums.
+ */
+// phpcs:set Yoast.NamingConventions.ValidHookName max_words 5
+// phpcs:set Yoast.NamingConventions.ValidHookName recommended_max_words 2
+
+apply_filters( 'Yoast\WP\Plugin\some_hook', $var ); // OK.
+
+do_action( 'Yoast\WP\Plugin\some_hook_name', $var ); // Warning - over recommended length.
+do_action_ref_array( 'Yoast\WP\Plugin\some_hook_name_which_is_too_long', $var ); // Error.
+
+do_action( 'yoast_plugin_some_hook_name', $var ); // Warning x 2 - over recommended length + wrong prefix.
+do_action_ref_array( 'yoast_plugin_some_hook_name_which_is_too_long', $var ); // Error - length + warning - wrong prefix..
+
+/*
+ * Incorrect custom settings (soft > max).
+ */
+// phpcs:set Yoast.NamingConventions.ValidHookName max_words 2
+// phpcs:set Yoast.NamingConventions.ValidHookName recommended_max_words 5
+
+do_action( 'Yoast\WP\Plugin\some_hook_name', $var ); // Error.
+
+// Reset to default settings.
+// phpcs:set Yoast.NamingConventions.ValidHookName prefixes[]
+// phpcs:set Yoast.NamingConventions.ValidHookName max_words 4
+// phpcs:set Yoast.NamingConventions.ValidHookName recommended_max_words 4
diff --git a/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php b/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php
new file mode 100644
index 00000000..d978c37e
--- /dev/null
+++ b/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php
@@ -0,0 +1,83 @@
+warningSeverity = 3;
+ }
+
+ /**
+ * Returns the lines where errors should occur.
+ *
+ * @return array =>
+ */
+ public function getErrorList() {
+
+ return [
+ 14 => 1,
+ 15 => 1,
+ 17 => 1,
+ 18 => 1,
+ 19 => 1,
+ 34 => 1,
+ 35 => 1,
+ 42 => 1,
+ 83 => 1,
+ 84 => 1,
+ 96 => 1,
+ 97 => 1,
+ 108 => 1,
+ 111 => 1,
+ 119 => 1,
+ ];
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @return array =>
+ */
+ public function getWarningList() {
+ return [
+ 16 => 1,
+ 19 => 1,
+ 33 => 1,
+ 35 => 1,
+ 42 => 2,
+ 45 => 1, // Severity: 3.
+ 48 => 1, // Severity: 3.
+ 72 => 1, // Severity: 3.
+ 73 => 2, // Severity: 3 + 5.
+ 81 => 1,
+ 84 => 1,
+ 94 => 1,
+ 97 => 1,
+ 107 => 1,
+ 110 => 2,
+ 111 => 1,
+ ];
+ }
+}
+
diff --git a/Yoast/Tests/WhiteSpace/FunctionSpacingUnitTest.php b/Yoast/Tests/WhiteSpace/FunctionSpacingUnitTest.php
index d4061549..228018bf 100644
--- a/Yoast/Tests/WhiteSpace/FunctionSpacingUnitTest.php
+++ b/Yoast/Tests/WhiteSpace/FunctionSpacingUnitTest.php
@@ -21,7 +21,7 @@ class FunctionSpacingUnitTest extends AbstractSniffUnitTest {
* @return array =>
*/
public function getErrorList() {
- return array(
+ return [
31 => 1,
33 => 1,
39 => 1,
@@ -32,7 +32,7 @@ public function getErrorList() {
74 => 1,
87 => 2,
88 => 1,
- );
+ ];
}
/**
@@ -41,6 +41,6 @@ public function getErrorList() {
* @return array =>
*/
public function getWarningList() {
- return array();
+ return [];
}
}
diff --git a/Yoast/Tests/Yoast/AlternativeFunctionsUnitTest.php b/Yoast/Tests/Yoast/AlternativeFunctionsUnitTest.php
index dc4fd96c..78c4b149 100644
--- a/Yoast/Tests/Yoast/AlternativeFunctionsUnitTest.php
+++ b/Yoast/Tests/Yoast/AlternativeFunctionsUnitTest.php
@@ -19,7 +19,7 @@ class AlternativeFunctionsUnitTest extends AbstractSniffUnitTest {
* @return array =>
*/
public function getErrorList() {
- return array(
+ return [
12 => 1,
13 => 1,
14 => 1,
@@ -27,7 +27,7 @@ public function getErrorList() {
17 => 1,
21 => 1,
22 => 1,
- );
+ ];
}
/**
@@ -36,6 +36,6 @@ public function getErrorList() {
* @return array =>
*/
public function getWarningList() {
- return array();
+ return [];
}
}
diff --git a/Yoast/Utils/CustomPrefixesTrait.php b/Yoast/Utils/CustomPrefixesTrait.php
new file mode 100644
index 00000000..3124667b
--- /dev/null
+++ b/Yoast/Utils/CustomPrefixesTrait.php
@@ -0,0 +1,147 @@
+previous_prefixes === $this->prefixes ) {
+ return;
+ }
+
+ // Set the cache *before* validation so as to not break the above compare.
+ $this->previous_prefixes = $this->prefixes;
+
+ $prefixes = (array) $this->prefixes;
+ $prefixes = array_filter( array_map( 'trim', $prefixes ) );
+
+ if ( empty( $prefixes ) ) {
+ $this->validated_prefixes = [];
+ return;
+ }
+
+ // Allow sniffs to add extra rules.
+ $prefixes = $this->filter_prefixes( $prefixes );
+
+ $validated = [];
+ foreach ( $prefixes as $prefix ) {
+ if ( strpos( $prefix, '\\' ) !== false ) {
+ $prefix = trim( $prefix, '\\' );
+ $validated[] = $prefix . '\\';
+ }
+ else {
+ // Old-style prefix.
+ $prefix = trim( $prefix, '_' );
+ $validated[] = $prefix . '_';
+ }
+ }
+
+ // Use reverse natural sorting to get the longest prefix first.
+ rsort( $validated, ( SORT_NATURAL | SORT_FLAG_CASE ) );
+
+ // Set the validated prefixes cache.
+ $this->validated_prefixes = $validated;
+ }
+
+ /**
+ * Overloadable method to do custom prefix filtering prior to validation.
+ *
+ * @param array $prefixes The unvalidated prefixes.
+ *
+ * @return array
+ */
+ protected function filter_prefixes( $prefixes ) {
+ return $prefixes;
+ }
+
+ /**
+ * Filter out all prefixes which don't contain a namespace separator.
+ *
+ * @param array $prefixes The unvalidated prefixes.
+ *
+ * @return array
+ */
+ protected function filter_allow_only_namespace_prefixes( $prefixes ) {
+ $filtered = [];
+ foreach ( $prefixes as $prefix ) {
+ if ( strpos( $prefix, '\\' ) === false ) {
+ continue;
+ }
+
+ $filtered[] = $prefix;
+ }
+
+ return $filtered;
+ }
+
+ /**
+ * Filter out all prefixes which only contain lowercase characters.
+ *
+ * @param array $prefixes The unvalidated prefixes.
+ *
+ * @return array
+ */
+ protected function filter_exclude_lowercase_prefixes( $prefixes ) {
+ $filtered = [];
+ foreach ( $prefixes as $prefix ) {
+ if ( strtolower( $prefix ) === $prefix ) {
+ continue;
+ }
+
+ $filtered[] = $prefix;
+ }
+
+ return $filtered;
+ }
+}
diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml
index 652991f1..9aaa2554 100644
--- a/Yoast/ruleset.xml
+++ b/Yoast/ruleset.xml
@@ -28,10 +28,10 @@
-->
-
+
@@ -45,6 +45,9 @@
+
+
+
+
+
@@ -108,12 +114,19 @@
+
+
+
+
+
+
+
-
+
*\.php$
@@ -137,22 +150,45 @@
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/composer.json b/composer.json
index e1ca36b2..91860281 100644
--- a/composer.json
+++ b/composer.json
@@ -23,15 +23,17 @@
},
"require": {
"php": ">=5.4",
- "squizlabs/php_codesniffer": "^3.4.2",
- "wp-coding-standards/wpcs": "^2.1.1",
- "phpcompatibility/phpcompatibility-wp": "^2.0.0",
+ "squizlabs/php_codesniffer": "^3.5.0",
+ "wp-coding-standards/wpcs": "^2.2.0",
+ "phpcompatibility/phpcompatibility-wp": "^2.1.0",
"dealerdirect/phpcodesniffer-composer-installer": "^0.5.0"
},
"require-dev": {
"phpcompatibility/php-compatibility": "^9.2.0",
"roave/security-advisories": "dev-master",
- "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0",
+ "jakub-onderka/php-parallel-lint": "^1.0",
+ "jakub-onderka/php-console-highlighter": "^0.4"
},
"minimum-stability": "dev",
"prefer-stable": true,
@@ -40,6 +42,9 @@
"Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run",
"@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --config-set default_standard Yoast"
],
+ "lint": [
+ "@php ./vendor/jakub-onderka/php-parallel-lint/parallel-lint . -e php --exclude vendor"
+ ],
"check-cs": [
"@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --runtime-set testVersion 5.4-"
],