From 8d7372d2bef48ad091f76bbeef6afba01578fa0c Mon Sep 17 00:00:00 2001
From: jrfnl An abstract Features
UtilityMethodTestCase
class to allow for testing your own utility methods written for PHP_CodeSniffer with ease.
- Compatible with both PHPCS 2.x as well as 3.x. Supports PHPUnit 4.x up to 8.x.
+ * 0 => array(
+ * 'type' => string, // The type declaration for the exception being caught.
+ * 'type_token' => integer, // The stack pointer to the start of the type declaration.
+ * 'type_end_token' => integer, // The stack pointer to the end of the type declaration.
+ * )
+ *
+ *
+ * @since 1.0.0
+ *
+ * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
+ * @param int $stackPtr The position of the token we are checking.
+ *
+ * @return array
+ *
+ * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is not of
+ * type T_CATCH or doesn't exist or in case
+ * of a parse error.
+ */
+ public static function getCaughtExceptions(File $phpcsFile, $stackPtr)
+ {
+ $tokens = $phpcsFile->getTokens();
+
+ if (isset($tokens[$stackPtr]) === false
+ || $tokens[$stackPtr]['code'] !== \T_CATCH
+ ) {
+ throw new RuntimeException('$stackPtr must be of type T_CATCH');
+ }
+
+ if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) {
+ throw new RuntimeException('Parentheses opener/closer of the T_CATCH could not be determined');
+ }
+
+ $opener = $tokens[$stackPtr]['parenthesis_opener'];
+ $closer = $tokens[$stackPtr]['parenthesis_closer'];
+ $exceptions = [];
+
+ $foundName = '';
+ $firstToken = null;
+ $lastToken = null;
+
+ for ($i = ($opener + 1); $i < $closer; $i++) {
+ if (isset(Tokens::$emptyTokens[$tokens[$i]['code']])) {
+ continue;
+ }
+
+ if (isset(Collections::$OONameTokens[$tokens[$i]['code']]) === false) {
+ // Add the current exception to the result array.
+ $exceptions[] = [
+ 'type' => $foundName,
+ 'type_token' => $firstToken,
+ 'type_end_token' => $lastToken,
+ ];
+
+ if ($tokens[$i]['code'] === \T_BITWISE_OR) {
+ // Multi-catch. Reset and continue.
+ $foundName = '';
+ $firstToken = null;
+ $lastToken = null;
+ continue;
+ }
+
+ break;
+ }
+
+ if (isset($firstToken) === false) {
+ $firstToken = $i;
+ }
+
+ $foundName .= $tokens[$i]['content'];
+ $lastToken = $i;
+ }
+
+ return $exceptions;
+ }
}
diff --git a/Tests/Utils/ControlStructures/GetCaughtExceptionsTest.inc b/Tests/Utils/ControlStructures/GetCaughtExceptionsTest.inc
new file mode 100644
index 00000000..b1b45855
--- /dev/null
+++ b/Tests/Utils/ControlStructures/GetCaughtExceptionsTest.inc
@@ -0,0 +1,32 @@
+expectPhpcsException('$stackPtr must be of type T_CATCH');
+ ControlStructures::getCaughtExceptions(self::$phpcsFile, 10000);
+ }
+
+ /**
+ * Test receiving an expected exception when a non-CATCH token is passed.
+ *
+ * @return void
+ */
+ public function testNotCatch()
+ {
+ $this->expectPhpcsException('$stackPtr must be of type T_CATCH');
+
+ $target = $this->getTargetToken('/* testNotCatch */', \T_TRY);
+ ControlStructures::getCaughtExceptions(self::$phpcsFile, $target);
+ }
+
+ /**
+ * Test receiving an expected exception when a parse error is encountered.
+ *
+ * @return void
+ */
+ public function testParseError()
+ {
+ $this->expectPhpcsException('Parentheses opener/closer of the T_CATCH could not be determined');
+
+ $target = $this->getTargetToken('/* testLiveCoding */', \T_CATCH);
+ ControlStructures::getCaughtExceptions(self::$phpcsFile, $target);
+ }
+
+ /**
+ * Test retrieving the exceptions caught in a `catch` condition.
+ *
+ * @dataProvider dataGetCaughtExceptions
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ * @param array $expected The expected return value.
+ *
+ * @return void
+ */
+ public function testGetCaughtExceptions($testMarker, $expected)
+ {
+ $stackPtr = $this->getTargetToken($testMarker, \T_CATCH);
+
+ // Translate offsets to absolute token positions.
+ foreach ($expected as $key => $value) {
+ $expected[$key]['type_token'] += $stackPtr;
+ $expected[$key]['type_end_token'] += $stackPtr;
+ }
+
+ $result = ControlStructures::getCaughtExceptions(self::$phpcsFile, $stackPtr);
+ $this->assertSame($expected, $result);
+ }
+
+ /**
+ * Data provider.
+ *
+ * @see testGetCaughtExceptions() For the array format.
+ *
+ * @return array
+ */
+ public function dataGetCaughtExceptions()
+ {
+ return [
+ 'single-name-only' => [
+ 'target' => '/* testSingleCatchNameOnly */',
+ 'expected' => [
+ [
+ 'type' => 'RuntimeException',
+ 'type_token' => 3,
+ 'type_end_token' => 3,
+ ],
+ ],
+ ],
+ 'single-name-leading-backslash' => [
+ 'target' => '/* testSingleCatchNameLeadingBackslash */',
+ 'expected' => [
+ [
+ 'type' => '\RuntimeException',
+ 'type_token' => 3,
+ 'type_end_token' => 4,
+ ],
+ ],
+ ],
+ 'single-partially-qualified' => [
+ 'target' => '/* testSingleCatchPartiallyQualified */',
+ 'expected' => [
+ [
+ 'type' => 'MyNS\RuntimeException',
+ 'type_token' => 4,
+ 'type_end_token' => 6,
+ ],
+ ],
+ ],
+ 'single-fully-qualified' => [
+ 'target' => '/* testSingleCatchFullyQualified */',
+ 'expected' => [
+ [
+ 'type' => '\MyNS\RuntimeException',
+ 'type_token' => 4,
+ 'type_end_token' => 7,
+ ],
+ ],
+ ],
+ 'single-name-with-comments-whitespace' => [
+ 'target' => '/* testSingleCatchPartiallyQualifiedWithCommentAndWhitespace */',
+ 'expected' => [
+ [
+ 'type' => 'My\NS\Sub\RuntimeException',
+ 'type_token' => 4,
+ 'type_end_token' => 15,
+ ],
+ ],
+ ],
+ 'single-namespace-operator' => [
+ 'target' => '/* testSingleCatchNamespaceOperator */',
+ 'expected' => [
+ [
+ 'type' => 'namespace\RuntimeException',
+ 'type_token' => 4,
+ 'type_end_token' => 6,
+ ],
+ ],
+ ],
+ 'multi-unqualified-names' => [
+ 'target' => '/* testMultiCatchSingleNames */',
+ 'expected' => [
+ [
+ 'type' => 'RuntimeException',
+ 'type_token' => 3,
+ 'type_end_token' => 3,
+ ],
+ [
+ 'type' => 'ParseErrorException',
+ 'type_token' => 7,
+ 'type_end_token' => 7,
+ ],
+ [
+ 'type' => 'AnotherException',
+ 'type_token' => 11,
+ 'type_end_token' => 11,
+ ],
+ ],
+ ],
+
+ 'multi-qualified-names' => [
+ 'target' => '/* testMultiCatchCompoundNames */',
+ 'expected' => [
+ [
+ 'type' => '\NS\RuntimeException',
+ 'type_token' => 3,
+ 'type_end_token' => 6,
+ ],
+ [
+ 'type' => 'My\ParseErrorException',
+ 'type_token' => 10,
+ 'type_end_token' => 12,
+ ],
+ [
+ 'type' => 'namespace\AnotherException',
+ 'type_token' => 16,
+ 'type_end_token' => 20,
+ ],
+ ],
+ ],
+ ];
+ }
+}
From 1b9a7fa7609bbe0ff05aac67349ba96e4c676e2d Mon Sep 17 00:00:00 2001
From: jrfnl PHPCSUtils is a set of utilities to aid developers of sniffs for PHP CodeSniffer.
+PHPCSUtils is a set of utilities to aid developers of sniffs for PHP_CodeSniffer.
This package offers the following features:
@@ -59,40 +59,40 @@Now you won't have to anymore. This package allows you to use the latest version of those utility functions in all PHP_CodeSniffer versions from PHPCS 2.6.0 and up.
-These classes take some of the heavy lifting away for a number of frequently occuring sniff types.
+These classes take most of the heavy lifting away for some frequently occurring sniff types.
Collections of related tokens as often used and needed for sniffs. +
Collections of related tokens often-used and needed for sniffs.
These are additional "token groups" to compliment the ones available through the PHPCS native PHP_CodeSniffer\Util\Tokens
class.
Whether you need to split an array
into the individual items, are trying to determine which variables are being assigned to in a list()
or are figuring out whether a function has a docblock, PHPCSUtils got you covered!
Whether you need to split an array
into the individual items, are trying to determine which variables are being assigned to in a list()
or are figuring out whether a function has a DocBlock, PHPCSUtils has you covered!
Includes improved versions of the PHPCS native utility functions and plenty new utility functions.
+Includes improved versions of the PHPCS native utility functions and plenty of new utility functions.
These functions are, of course, compatible with PHPCS 2.6.0 up to PHPCS master
.
An abstract UtilityMethodTestCase
class to allow for testing your own utility methods written for PHP_CodeSniffer with ease.
+
An abstract UtilityMethodTestCase
class to support testing of your utility methods written for PHP_CodeSniffer.
Compatible with both PHPCS 2.x as well as 3.x. Supports PHPUnit 4.x up to 9.x.
A PHPCS23Utils
standard which allows sniffs to work in both PHPCS 2.x and 3.x, as well as a number of helper functions for external standards which still want to support both PHP_CodeSniffer 2.x as well as 3.x.
A PHPCS23Utils
standard which allows sniffs to work in both PHPCS 2.x and 3.x, as well as a few helper functions for external standards which still want to support both PHP_CodeSniffer 2.x as well as 3.x.
To see detailed information about all available abstract sniffs, utility functions and PHPCS helper functions, have a read through the extensive documentation.
+To see detailed information about all the available abstract sniffs, utility functions and PHPCS helper functions, have a read through the extensive documentation.
If your external PHP_CodeSniffer standard only supports Composer based installs and has a minimum PHPCS requirement of PHP_CodeSniffer 3.1.0, integrating PHPCSUtils is pretty straight forward.
+If your external PHP_CodeSniffer standard only supports Composer-based installs and has a minimum PHPCS requirement of PHP_CodeSniffer 3.1.0, integrating PHPCSUtils is pretty straight forward.
Run the following from the root of your external PHPCS standard's project:
composer require phpcsstandards/phpcsutils:"^1.0"
@@ -122,19 +122,19 @@ Composer based with a minimum PHPCS requirement of PHPCS 3.1.0
This plugin will automatically register PHPCSUtils (and your own external standard) with PHP_CodeSniffer, so you and your users don't have to worry about this anymore.
- Note: if your end-user installation instructions include instructions on adding a Composer PHPCS plugin or on manually registering your standard with PHPCS using the --config-set installed_paths
command, you may want to remove those instructions as they are no longer needed.
+ Note: if your end-user installation instructions include instructions on adding a Composer PHPCS plugin or on manually registering your standard with PHPCS using the --config-set installed_paths
command, you can remove those instructions as they are no longer needed.
Running your unit tests
If your unit tests use the PHP_CodeSniffer native unit test suite, all is good.
- If you have your own unit test suite to test your sniffs, make sure to load the composer vendor/autoload.php
file in your PHPUnit bootstrap file or as the PHPUnit bootstrap file.
+ If you have your own unit test suite to test your sniffs, make sure to load the Composer vendor/autoload.php
file in your PHPUnit bootstrap file or as the PHPUnit bootstrap file.
If you intend to use the test utilities provided in the PHPCSUtils/TestUtils
directory, make sure you also load the vendor/phpcsstandards/phpcsutils/phpcsutils-autoload.php
file in your PHPUnit bootstrap file.
- Composer based with a minimum PHPCS requirement of PHPCS 2.6.0
+ Composer-based with a minimum PHPCS requirement of PHPCS 2.6.0
Follow the above instructions for use with PHPCS 3.x.
@@ -151,23 +151,23 @@ Running your unit tests
If your standard supports both PHPCS 2.x as well as 3.x, you are bound to already have a PHPUnit bootstrap.php
file in place.
- To allow the unit tests to find the relevant files for PHPCSUtils, make sure that the bootstrap loads both the composer vendor/autoload.php
file, as well as the vendor/phpcsstandards/phpcsutils/phpcsutils-autoload.php
file.
+ To allow the unit tests to find the relevant files for PHPCSUtils, make sure that the bootstrap loads both the Composer vendor/autoload.php
file, as well as the vendor/phpcsstandards/phpcsutils/phpcsutils-autoload.php
file.
Frequently Asked Questions
- Q: How does this all work without an external standard needing to register an autoloader ?
+ Q: How does this all work without an external standard needing to register an autoloader?
A: As PHPCSUtils is registered with PHPCS as an external standard and PHPCSUtils complies with the naming requirements of PHPCS, the PHPCS native autoloader will automatically take care of loading the classes you use from PHPCSUtils.
- Q: What does the PHPCS23Utils
standard do ?
+ Q: What does the PHPCS23Utils
standard do?
A: All the PHPCS23Utils
standard does is load the phpcsutils-autoload.php
file.
PHPCS 3.x uses namespaces, while PHPCS 2.x does not. The phpcsutils-autoload.php
file creates class_alias
-es for the most commonly used PHPCS classes, including all PHPCS classes used by PHPCSUtils. That way, both your external standard as well as PHPCSUtils can refer to the PHPCS 3.x class names and the code will still work in PHPCS 2.x.
- Q: Why is PHP_CodeSniffer 3.5.3 not supported ?
+ Q: Why is PHP_CodeSniffer 3.5.3 not supported?
A: The backfill for PHP 7.4 numeric literals with underscores in PHP_CodeSniffer 3.5.3 is broken and there is no way to reliably provide support for anything to do with numbers or T_STRING
tokens when using PHP_CodeSniffer 3.5.3 as the tokens returned by the tokenizer are unpredictable and unreliable.
@@ -178,7 +178,7 @@ Contributing
Contributions to this project are welcome. Clone the repo, branch off from develop
, make your changes, commit them and send in a pull request.
- If unsure whether the changes you are proposing would be welcome, open an issue first to discuss your proposal.
+ If you are unsure whether the changes you are proposing would be welcome, please open an issue first to discuss your proposal.
License
From 585f69d653322180a94eb958f366f01ed34785d9 Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Sun, 29 Mar 2020 21:03:59 +0200
Subject: [PATCH 35/79] FunctionDeclarations::isMagicFunction(): bug fix for
nested functions
Function declarations nested in a class method declare the function in the global namespace.
See: https://3v4l.org/R32Sl
This was no handled correctly by the function so far. Fixed now.
Includes unit tests covering the fix.
---
PHPCSUtils/Utils/FunctionDeclarations.php | 3 +-
.../SpecialFunctionsTest.inc | 13 +++++++
.../SpecialFunctionsTest.php | 37 +++++++++++++++++++
3 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/PHPCSUtils/Utils/FunctionDeclarations.php b/PHPCSUtils/Utils/FunctionDeclarations.php
index edf222e1..54382489 100644
--- a/PHPCSUtils/Utils/FunctionDeclarations.php
+++ b/PHPCSUtils/Utils/FunctionDeclarations.php
@@ -16,7 +16,6 @@
use PHPCSUtils\BackCompat\BCTokens;
use PHPCSUtils\BackCompat\Helper;
use PHPCSUtils\Tokens\Collections;
-use PHPCSUtils\Utils\Conditions;
use PHPCSUtils\Utils\GetTokensAsString;
use PHPCSUtils\Utils\ObjectDeclarations;
use PHPCSUtils\Utils\Scopes;
@@ -819,7 +818,7 @@ public static function isMagicFunction(File $phpcsFile, $stackPtr)
return false;
}
- if (Conditions::hasCondition($phpcsFile, $stackPtr, BCTokens::ooScopeTokens()) === true) {
+ if (Scopes::isOOMethod($phpcsFile, $stackPtr) === true) {
return false;
}
diff --git a/Tests/Utils/FunctionDeclarations/SpecialFunctionsTest.inc b/Tests/Utils/FunctionDeclarations/SpecialFunctionsTest.inc
index 374c28da..ffc59a77 100644
--- a/Tests/Utils/FunctionDeclarations/SpecialFunctionsTest.inc
+++ b/Tests/Utils/FunctionDeclarations/SpecialFunctionsTest.inc
@@ -94,3 +94,16 @@ interface MagicInterface
/* testMethodInInterfaceNotMagicName */
function __myFunction();
}
+
+// Verify that nested functions are correctly seen as declared in the global namespace.
+class FunctionsDeclaredNestedInMethod extends \SoapClient {
+ /* testNonMagicMethod */
+ public function methodName() {
+ /* testNestedFunctionDeclarationMagicFunction */
+ function __autoload($class) {}
+ /* testNestedFunctionDeclarationNonMagicFunction */
+ function __isset() {}
+ /* testNestedFunctionDeclarationNonSpecialFunction */
+ function __getLastResponse() {}
+ }
+}
diff --git a/Tests/Utils/FunctionDeclarations/SpecialFunctionsTest.php b/Tests/Utils/FunctionDeclarations/SpecialFunctionsTest.php
index 10adfcfe..ae747601 100644
--- a/Tests/Utils/FunctionDeclarations/SpecialFunctionsTest.php
+++ b/Tests/Utils/FunctionDeclarations/SpecialFunctionsTest.php
@@ -391,6 +391,43 @@ public function dataItsAKindOfMagic()
'special' => false,
],
],
+
+ 'NonMagicMethod' => [
+ '/* testNonMagicMethod */',
+ [
+ 'function' => false,
+ 'method' => false,
+ 'double' => false,
+ 'special' => false,
+ ],
+ ],
+ 'NestedFunctionDeclarationMagicFunction' => [
+ '/* testNestedFunctionDeclarationMagicFunction */',
+ [
+ 'function' => true,
+ 'method' => false,
+ 'double' => false,
+ 'special' => false,
+ ],
+ ],
+ 'NestedFunctionDeclarationNonMagicFunction' => [
+ '/* testNestedFunctionDeclarationNonMagicFunction */',
+ [
+ 'function' => false,
+ 'method' => false,
+ 'double' => false,
+ 'special' => false,
+ ],
+ ],
+ 'NestedFunctionDeclarationNonSpecialFunction' => [
+ '/* testNestedFunctionDeclarationNonSpecialFunction */',
+ [
+ 'function' => false,
+ 'method' => false,
+ 'double' => false,
+ 'special' => false,
+ ],
+ ],
];
}
}
From 5a75cc5c1e83e2b840a1eee9757bbc3cdab259dc Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Tue, 31 Mar 2020 19:54:47 +0200
Subject: [PATCH 36/79] Travis: fix the build
The Travis docs say that `$TRAVIS_BUILD_STAGE_NAME` is in "proper case" form:
> TRAVIS_BUILD_STAGE_NAME: The build stage in capitalized form, e.g. Test or Deploy. If a build does not use build stages, this variable is empty ("").
However, it looks like they made an (undocumented) change (probably a bug in their script handling) which means that the `$TRAVIS_BUILD_STAGE_NAME` name is now in the case as given, which in this case is _lowercase_.
This means that some of the comparisons are failing and the wrong things are executed for certain builds.
As I expect this to be a bug in Travis, I'm not changing the case for the comparisons at this time.
Instead I'm fixing this by inline fixing the case of the variable for the comparisons.
Refs:
* https://docs.travis-ci.com/user/environment-variables#default-environment-variables (near the bottom of the list)
---
.travis.yml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index ccb79674..c83d89cb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -141,14 +141,14 @@ jobs:
before_install:
# Speed up build time by disabling Xdebug when its not needed.
- |
- if [[ "$TRAVIS_BUILD_STAGE_NAME" != "Coverage" ]]; then
+ if [[ "${TRAVIS_BUILD_STAGE_NAME^}" != "Coverage" ]]; then
phpenv config-rm xdebug.ini || echo 'No xdebug config.'
fi
# On stable PHPCS versions, allow for PHP deprecation notices.
# Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore.
- |
- if [[ "$TRAVIS_BUILD_STAGE_NAME" != "Sniff" && $PHPCS_VERSION != "dev-master" && "$PHPCS_VERSION" != "n/a" ]]; then
+ if [[ "${TRAVIS_BUILD_STAGE_NAME^}" != "Sniff" && $PHPCS_VERSION != "dev-master" && "$PHPCS_VERSION" != "n/a" ]]; then
echo 'error_reporting = E_ALL & ~E_DEPRECATED' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
fi
@@ -162,7 +162,7 @@ install:
composer require --no-update --no-scripts squizlabs/php_codesniffer:${PHPCS_VERSION}
fi
- |
- if [[ "$TRAVIS_BUILD_STAGE_NAME" == "Coverage" ]]; then
+ if [[ "${TRAVIS_BUILD_STAGE_NAME^}" == "Coverage" ]]; then
composer require --dev --no-update --no-suggest --no-scripts php-coveralls/php-coveralls:${COVERALLS_VERSION}
fi
- |
@@ -177,7 +177,7 @@ install:
before_script:
- - if [[ "$TRAVIS_BUILD_STAGE_NAME" == "Coverage" ]]; then mkdir -p build/logs; fi
+ - if [[ "${TRAVIS_BUILD_STAGE_NAME^}" == "Coverage" ]]; then mkdir -p build/logs; fi
- phpenv rehash
@@ -187,18 +187,18 @@ script:
# Run the unit tests.
- |
- if [[ $PHPCS_VERSION != "n/a" && "$TRAVIS_BUILD_STAGE_NAME" != "Coverage" ]]; then
+ if [[ $PHPCS_VERSION != "n/a" && "${TRAVIS_BUILD_STAGE_NAME^}" != "Coverage" ]]; then
composer test
- elif [[ $PHPCS_VERSION != "n/a" && "$TRAVIS_BUILD_STAGE_NAME" == "Coverage" ]]; then
+ elif [[ $PHPCS_VERSION != "n/a" && "${TRAVIS_BUILD_STAGE_NAME^}" == "Coverage" ]]; then
composer coverage
fi
after_success:
- |
- if [[ "$TRAVIS_BUILD_STAGE_NAME" == "Coverage" && $COVERALLS_VERSION == "^1.0" ]]; then
+ if [[ "${TRAVIS_BUILD_STAGE_NAME^}" == "Coverage" && $COVERALLS_VERSION == "^1.0" ]]; then
php vendor/bin/coveralls -v -x build/logs/clover.xml
fi
- |
- if [[ "$TRAVIS_BUILD_STAGE_NAME" == "Coverage" && $COVERALLS_VERSION == "^2.0" ]]; then
+ if [[ "${TRAVIS_BUILD_STAGE_NAME^}" == "Coverage" && $COVERALLS_VERSION == "^2.0" ]]; then
php vendor/bin/php-coveralls -v -x build/logs/clover.xml
fi
From 58abda8df0baecde9aab25714b5e5b238aa2af56 Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Tue, 31 Mar 2020 23:05:00 +0200
Subject: [PATCH 37/79] Lists::getAssignments(): improve the return value
:warning: **BREAKING CHANGE** :warning:
In the initial commit, the `Lists::getAssignments()` method would have three different arrays as potential output.
1. An array with just the `raw` and `is_empty` keys for an empty list item.
2. An array with the above + detailed information about the assignments for non-keyed list items.
3. An array with the above + detail information about the keys found for keyed list items.
This meant that - aside from for the `raw` and `is_empty` keys -, a dev looping over the list items would always need to do an `isset()` check for each array index before the value could be compared or used.
That made the method return value fiddly to work with.
In this commit, the above three different arrays is now reduced to two different arrays (type 2 and 3) with either an empty string or `false` as the default value for all keys.
Additionally:
* The `nested_list` index key has been renamed to `is_nested_list` to make it clearer that this will be a boolean value and not the detailed information about the nested list.
* And while this will rarely be relevant for sniffs implementing this method, the order of the array indexes in the return array has also been adjusted.
Includes updated unit tests.
---
PHPCSUtils/Utils/Lists.php | 90 ++--
Tests/Utils/Lists/GetAssignmentsTest.php | 613 +++++++++++++----------
2 files changed, 414 insertions(+), 289 deletions(-)
diff --git a/PHPCSUtils/Utils/Lists.php b/PHPCSUtils/Utils/Lists.php
index 0623722f..4ffc0eee 100644
--- a/PHPCSUtils/Utils/Lists.php
+++ b/PHPCSUtils/Utils/Lists.php
@@ -25,6 +25,27 @@
class Lists
{
+ /**
+ * Default values for individual list items.
+ *
+ * Used by the `getAssignments()` method.
+ *
+ * @since 1.0.0
+ *
+ * @var array
+ */
+ private static $listItemDefaults = [
+ 'raw' => '',
+ 'assignment' => '',
+ 'is_empty' => false,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ ];
+
/**
* Determine whether a `T_OPEN/CLOSE_SHORT_ARRAY` token is a short list() construct.
*
@@ -229,27 +250,23 @@ public static function getOpenClose(File $phpcsFile, $stackPtr, $isShortList = n
*
*
* 0 => array(
- * 'raw' => string, // The full content of the variable definition, including
- * // whitespace and comments.
- * // This may be an empty string when an item is being skipped.
- * 'is_empty' => bool, // Whether this is an empty list item, i.e. the
- * // second item in `list($a, , $b)`.
- * )
- *
- *
- * Non-empty list items will have the following additional array indexes set:
- *
- * 'assignment' => string, // The content of the assignment part, cleaned of comments.
- * // This could be a nested list.
- * 'nested_list' => bool, // Whether this is a nested list.
+ * 'raw' => string, // The full content of the variable definition, including
+ * // whitespace and comments.
+ * // This may be an empty string when an item is being skipped.
+ * 'assignment' => string, // The content of the assignment part, _cleaned of comments_.
+ * // This may be an empty string for an empty list item;
+ * // it could also be a nested list represented as a string.
+ * 'is_empty' => bool, // Whether this is an empty list item, i.e. the
+ * // second item in `list($a, , $b)`.
+ * 'is_nested_list' => bool, // Whether this is a nested list.
+ * 'variable' => string|false, // The base variable being assigned to or
+ * // FALSE in case of a nested list or avariable variable.
+ * // I.e. `$a` in `list($a['key'])`.
+ * 'assignment_token' => int|false, // The start pointer for the assignment.
+ * 'assignment_end_token' => int|false, // The end pointer for the assignment.
* 'assign_by_reference' => bool, // Is the variable assigned by reference?
* 'reference_token' => int|false, // The stack pointer to the reference operator or
* // FALSE when not a reference assignment.
- * 'variable' => string|false, // The base variable being assigned to or
- * // FALSE in case of a nested list or variable variable.
- * // I.e. `$a` in `list($a['key'])`.
- * 'assignment_token' => int, // The start pointer for the assignment.
- * 'assignment_end_token' => int, // The end pointer for the assignment.
*
*
*
@@ -293,7 +310,7 @@ public static function getAssignments(File $phpcsFile, $stackPtr)
$reference = null;
$list = null;
$lastComma = $opener;
- $current = [];
+ $keys = [];
for ($i = ($opener + 1); $i <= $closer; $i++) {
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']])) {
@@ -302,11 +319,10 @@ public static function getAssignments(File $phpcsFile, $stackPtr)
switch ($tokens[$i]['code']) {
case \T_DOUBLE_ARROW:
- $current['key'] = GetTokensAsString::compact($phpcsFile, $start, $lastNonEmpty, true);
-
- $current['key_token'] = $start;
- $current['key_end_token'] = $lastNonEmpty;
- $current['double_arrow_token'] = $i;
+ $keys['key'] = GetTokensAsString::compact($phpcsFile, $start, $lastNonEmpty, true);
+ $keys['key_token'] = $start;
+ $keys['key_end_token'] = $lastNonEmpty;
+ $keys['double_arrow_token'] = $i;
// Partial reset.
$start = null;
@@ -316,6 +332,7 @@ public static function getAssignments(File $phpcsFile, $stackPtr)
case \T_COMMA:
case $tokens[$closer]['code']:
+ // Check if this is the end of the list or only a token with the same type as the list closer.
if ($tokens[$i]['code'] === $tokens[$closer]['code']) {
if ($i !== $closer) {
$lastNonEmpty = $i;
@@ -326,23 +343,17 @@ public static function getAssignments(File $phpcsFile, $stackPtr)
}
}
+ // Ok, so this is actually the end of the list item.
+ $current = self::$listItemDefaults;
$current['raw'] = \trim(GetTokensAsString::normal($phpcsFile, ($lastComma + 1), ($i - 1)));
if ($start === null) {
$current['is_empty'] = true;
} else {
- $current['is_empty'] = false;
- $current['assignment'] = \trim(
+ $current['assignment'] = \trim(
GetTokensAsString::compact($phpcsFile, $start, $lastNonEmpty, true)
);
- $current['nested_list'] = isset($list);
-
- $current['assign_by_reference'] = false;
- $current['reference_token'] = false;
- if (isset($reference)) {
- $current['assign_by_reference'] = true;
- $current['reference_token'] = $reference;
- }
+ $current['is_nested_list'] = isset($list);
$current['variable'] = false;
if (isset($list) === false && $tokens[$start]['code'] === \T_VARIABLE) {
@@ -350,6 +361,15 @@ public static function getAssignments(File $phpcsFile, $stackPtr)
}
$current['assignment_token'] = $start;
$current['assignment_end_token'] = $lastNonEmpty;
+
+ if (isset($reference)) {
+ $current['assign_by_reference'] = true;
+ $current['reference_token'] = $reference;
+ }
+ }
+
+ if (empty($keys) === false) {
+ $current += $keys;
}
$vars[] = $current;
@@ -360,7 +380,7 @@ public static function getAssignments(File $phpcsFile, $stackPtr)
$reference = null;
$list = null;
$lastComma = $i;
- $current = [];
+ $keys = [];
break;
diff --git a/Tests/Utils/Lists/GetAssignmentsTest.php b/Tests/Utils/Lists/GetAssignmentsTest.php
index bc7f3213..7876d464 100644
--- a/Tests/Utils/Lists/GetAssignmentsTest.php
+++ b/Tests/Utils/Lists/GetAssignmentsTest.php
@@ -105,10 +105,10 @@ public function testGetAssignments($testMarker, $targetToken, $expected)
if (isset($subset['reference_token']) && $subset['reference_token'] !== false) {
$expected[$index]['reference_token'] += $stackPtr;
}
- if (isset($subset['assignment_token'])) {
+ if (isset($subset['assignment_token']) && $subset['assignment_token'] !== false) {
$expected[$index]['assignment_token'] += $stackPtr;
}
- if (isset($subset['assignment_end_token'])) {
+ if (isset($subset['assignment_end_token']) && $subset['assignment_end_token'] !== false) {
$expected[$index]['assignment_end_token'] += $stackPtr;
}
}
@@ -145,19 +145,47 @@ public function dataGetAssignments()
[
0 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
1 => [
'raw' => '/* comment */',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
2 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
3 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
],
],
@@ -172,25 +200,25 @@ public function dataGetAssignments()
[
0 => [
'raw' => '$id',
- 'is_empty' => false,
'assignment' => '$id',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$id',
'assignment_token' => 2,
'assignment_end_token' => 2,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
1 => [
'raw' => '$name',
- 'is_empty' => false,
'assignment' => '$name',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$name',
'assignment_token' => 5,
'assignment_end_token' => 5,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
],
],
@@ -200,25 +228,25 @@ public function dataGetAssignments()
[
0 => [
'raw' => '$this->propA',
- 'is_empty' => false,
'assignment' => '$this->propA',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$this',
'assignment_token' => 1,
'assignment_end_token' => 3,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
1 => [
'raw' => '$this->propB',
- 'is_empty' => false,
'assignment' => '$this->propB',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$this',
'assignment_token' => 6,
'assignment_end_token' => 8,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
],
],
@@ -227,34 +255,34 @@ public function dataGetAssignments()
\T_OPEN_SHORT_ARRAY,
[
0 => [
- 'key' => "'id'",
- 'key_token' => 1,
- 'key_end_token' => 1,
- 'double_arrow_token' => 3,
'raw' => '\'id\' => & $id',
- 'is_empty' => false,
'assignment' => '$id',
- 'nested_list' => false,
- 'assign_by_reference' => true,
- 'reference_token' => 5,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$id',
'assignment_token' => 7,
'assignment_end_token' => 7,
+ 'assign_by_reference' => true,
+ 'reference_token' => 5,
+ 'key' => "'id'",
+ 'key_token' => 1,
+ 'key_end_token' => 1,
+ 'double_arrow_token' => 3,
],
1 => [
- 'key' => "'name'",
- 'key_token' => 10,
- 'key_end_token' => 10,
- 'double_arrow_token' => 12,
'raw' => '\'name\' => $name',
- 'is_empty' => false,
'assignment' => '$name',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$name',
'assignment_token' => 14,
'assignment_end_token' => 14,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => "'name'",
+ 'key_token' => 10,
+ 'key_end_token' => 10,
+ 'double_arrow_token' => 12,
],
],
],
@@ -264,25 +292,25 @@ public function dataGetAssignments()
[
0 => [
'raw' => '$a',
- 'is_empty' => false,
'assignment' => '$a',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$a',
'assignment_token' => 2,
'assignment_end_token' => 2,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
1 => [
'raw' => 'list($b, $c)',
- 'is_empty' => false,
'assignment' => 'list($b, $c)',
- 'nested_list' => true,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => true,
'variable' => false,
'assignment_token' => 5,
'assignment_end_token' => 11,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
],
],
@@ -291,49 +319,49 @@ public function dataGetAssignments()
\T_LIST,
[
0 => [
- 'key' => "'name'",
- 'key_token' => 2,
- 'key_end_token' => 2,
- 'double_arrow_token' => 4,
'raw' => '\'name\' => $a',
- 'is_empty' => false,
'assignment' => '$a',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$a',
'assignment_token' => 6,
'assignment_end_token' => 6,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => "'name'",
+ 'key_token' => 2,
+ 'key_end_token' => 2,
+ 'double_arrow_token' => 4,
],
1 => [
- 'key' => "'id'",
- 'key_token' => 9,
- 'key_end_token' => 9,
- 'double_arrow_token' => 11,
'raw' => '\'id\' => $b',
- 'is_empty' => false,
'assignment' => '$b',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$b',
'assignment_token' => 13,
'assignment_end_token' => 13,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => "'id'",
+ 'key_token' => 9,
+ 'key_end_token' => 9,
+ 'double_arrow_token' => 11,
],
2 => [
- 'key' => "'field'",
- 'key_token' => 16,
- 'key_end_token' => 16,
- 'double_arrow_token' => 18,
'raw' => '\'field\' => $c',
- 'is_empty' => false,
'assignment' => '$c',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$c',
'assignment_token' => 20,
'assignment_end_token' => 20,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => "'field'",
+ 'key_token' => 16,
+ 'key_end_token' => 16,
+ 'double_arrow_token' => 18,
],
],
],
@@ -343,56 +371,91 @@ public function dataGetAssignments()
[
0 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
1 => [
'raw' => '$a',
- 'is_empty' => false,
'assignment' => '$a',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$a',
'assignment_token' => 5,
'assignment_end_token' => 5,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
2 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
3 => [
'raw' => '$b',
- 'is_empty' => false,
'assignment' => '$b',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$b',
'assignment_token' => 10,
'assignment_end_token' => 10,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
4 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
5 => [
'raw' => '$c',
- 'is_empty' => false,
'assignment' => '$c',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$c',
'assignment_token' => 14,
'assignment_end_token' => 14,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
6 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
7 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
],
],
@@ -401,68 +464,75 @@ public function dataGetAssignments()
\T_LIST,
[
0 => [
- 'key' => '"name"',
- 'key_token' => 4,
- 'key_end_token' => 4,
- 'double_arrow_token' => 6,
'raw' => '"name" => $this->name',
- 'is_empty' => false,
'assignment' => '$this->name',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$this',
'assignment_token' => 8,
'assignment_end_token' => 10,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => '"name"',
+ 'key_token' => 4,
+ 'key_end_token' => 4,
+ 'double_arrow_token' => 6,
],
1 => [
- 'key' => '"colour"',
- 'key_token' => 14,
- 'key_end_token' => 14,
- 'double_arrow_token' => 16,
'raw' => '"colour" => $this->colour',
- 'is_empty' => false,
'assignment' => '$this->colour',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$this',
'assignment_token' => 18,
'assignment_end_token' => 20,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => '"colour"',
+ 'key_token' => 14,
+ 'key_end_token' => 14,
+ 'double_arrow_token' => 16,
],
2 => [
- 'key' => '"age"',
- 'key_token' => 24,
- 'key_end_token' => 24,
- 'double_arrow_token' => 26,
'raw' => '"age" => $this->age',
- 'is_empty' => false,
'assignment' => '$this->age',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$this',
'assignment_token' => 28,
'assignment_end_token' => 30,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => '"age"',
+ 'key_token' => 24,
+ 'key_end_token' => 24,
+ 'double_arrow_token' => 26,
],
3 => [
- 'key' => '"cuteness"',
- 'key_token' => 34,
- 'key_end_token' => 34,
- 'double_arrow_token' => 36,
'raw' => '"cuteness" => $this->cuteness',
- 'is_empty' => false,
'assignment' => '$this->cuteness',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$this',
'assignment_token' => 38,
'assignment_end_token' => 40,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => '"cuteness"',
+ 'key_token' => 34,
+ 'key_end_token' => 34,
+ 'double_arrow_token' => 36,
],
4 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
],
],
@@ -471,34 +541,34 @@ public function dataGetAssignments()
[\T_OPEN_SHORT_ARRAY, \T_OPEN_SQUARE_BRACKET],
[
0 => [
- 'key' => "'a'",
- 'key_token' => 1,
- 'key_end_token' => 1,
- 'double_arrow_token' => 3,
'raw' => '\'a\' => [&$a, $b]',
- 'is_empty' => false,
'assignment' => '[&$a, $b]',
- 'nested_list' => true,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => true,
'variable' => false,
'assignment_token' => 5,
'assignment_end_token' => 11,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => "'a'",
+ 'key_token' => 1,
+ 'key_end_token' => 1,
+ 'double_arrow_token' => 3,
],
1 => [
- 'key' => "'b'",
- 'key_token' => 14,
- 'key_end_token' => 14,
- 'double_arrow_token' => 16,
'raw' => '\'b\' => [$c, &$d]',
- 'is_empty' => false,
'assignment' => '[$c, &$d]',
- 'nested_list' => true,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => true,
'variable' => false,
'assignment_token' => 18,
'assignment_end_token' => 24,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => "'b'",
+ 'key_token' => 14,
+ 'key_end_token' => 14,
+ 'double_arrow_token' => 16,
],
],
],
@@ -508,36 +578,36 @@ public function dataGetAssignments()
[
0 => [
'raw' => '$a[]',
- 'is_empty' => false,
'assignment' => '$a[]',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$a',
'assignment_token' => 2,
'assignment_end_token' => 4,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
1 => [
'raw' => '$a[0]',
- 'is_empty' => false,
'assignment' => '$a[0]',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$a',
'assignment_token' => 7,
'assignment_end_token' => 10,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
2 => [
'raw' => '$a[]',
- 'is_empty' => false,
'assignment' => '$a[]',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$a',
'assignment_token' => 13,
'assignment_end_token' => 15,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
],
],
@@ -546,129 +616,136 @@ public function dataGetAssignments()
\T_OPEN_SHORT_ARRAY,
[
0 => [
- 'key' => "'a' . 'b'",
- 'key_token' => 3,
- 'key_end_token' => 7,
- 'double_arrow_token' => 9,
'raw' => '\'a\' . \'b\' => $a',
- 'is_empty' => false,
'assignment' => '$a',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$a',
'assignment_token' => 11,
'assignment_end_token' => 11,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => "'a' . 'b'",
+ 'key_token' => 3,
+ 'key_end_token' => 7,
+ 'double_arrow_token' => 9,
],
1 => [
- 'key' => '($a * 2)',
- 'key_token' => 15,
- 'key_end_token' => 21,
- 'double_arrow_token' => 24,
'raw' => '($a * 2)
=> $b->prop->prop /* comment */ [\'index\']',
- 'is_empty' => false,
'assignment' => '$b->prop->prop [\'index\']',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$b',
'assignment_token' => 26,
'assignment_end_token' => 36,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => '($a * 2)',
+ 'key_token' => 15,
+ 'key_end_token' => 21,
+ 'double_arrow_token' => 24,
],
2 => [
- 'key' => 'CONSTANT & OTHER',
- 'key_token' => 40,
- 'key_end_token' => 46,
- 'double_arrow_token' => 48,
'raw' => 'CONSTANT & /*comment*/ OTHER => $c',
- 'is_empty' => false,
'assignment' => '$c',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$c',
'assignment_token' => 50,
'assignment_end_token' => 50,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => 'CONSTANT & OTHER',
+ 'key_token' => 40,
+ 'key_end_token' => 46,
+ 'double_arrow_token' => 48,
],
3 => [
- 'key' => '(string) &$c',
- 'key_token' => 54,
- 'key_end_token' => 57,
- 'double_arrow_token' => 59,
'raw' => '(string) &$c => &$d["D"]',
- 'is_empty' => false,
'assignment' => '$d["D"]',
- 'nested_list' => false,
- 'assign_by_reference' => true,
- 'reference_token' => 61,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$d',
'assignment_token' => 62,
'assignment_end_token' => 65,
+ 'assign_by_reference' => true,
+ 'reference_token' => 61,
+ 'key' => '(string) &$c',
+ 'key_token' => 54,
+ 'key_end_token' => 57,
+ 'double_arrow_token' => 59,
],
4 => [
- 'key' => 'get_key()[1]',
- 'key_token' => 69,
- 'key_end_token' => 74,
- 'double_arrow_token' => 76,
'raw' => 'get_key()[1] => $e',
- 'is_empty' => false,
'assignment' => '$e',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$e',
'assignment_token' => 78,
'assignment_end_token' => 78,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => 'get_key()[1]',
+ 'key_token' => 69,
+ 'key_end_token' => 74,
+ 'double_arrow_token' => 76,
],
5 => [
- 'key' => '$prop[\'index\']',
- 'key_token' => 82,
- 'key_end_token' => 85,
- 'double_arrow_token' => 87,
'raw' => '$prop[\'index\'] => $f->prop[\'index\']',
- 'is_empty' => false,
'assignment' => '$f->prop[\'index\']',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$f',
'assignment_token' => 89,
'assignment_end_token' => 94,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => '$prop[\'index\']',
+ 'key_token' => 82,
+ 'key_end_token' => 85,
+ 'double_arrow_token' => 87,
],
6 => [
- 'key' => '$obj->fieldname',
- 'key_token' => 98,
- 'key_end_token' => 100,
- 'double_arrow_token' => 102,
'raw' => '$obj->fieldname => ${$g}',
- 'is_empty' => false,
'assignment' => '${$g}',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => false,
'assignment_token' => 104,
'assignment_end_token' => 107,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => '$obj->fieldname',
+ 'key_token' => 98,
+ 'key_end_token' => 100,
+ 'double_arrow_token' => 102,
],
7 => [
- 'key' => '$simple',
- 'key_token' => 111,
- 'key_end_token' => 111,
- 'double_arrow_token' => 113,
'raw' => '$simple => &$h',
- 'is_empty' => false,
'assignment' => '$h',
- 'nested_list' => false,
- 'assign_by_reference' => true,
- 'reference_token' => 115,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$h',
'assignment_token' => 116,
'assignment_end_token' => 116,
+ 'assign_by_reference' => true,
+ 'reference_token' => 115,
+ 'key' => '$simple',
+ 'key_token' => 111,
+ 'key_end_token' => 111,
+ 'double_arrow_token' => 113,
],
8 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
],
],
@@ -677,19 +754,19 @@ public function dataGetAssignments()
\T_LIST,
[
0 => [
- 'key' => 'get_key()[1]',
- 'key_token' => 2,
- 'key_end_token' => 7,
- 'double_arrow_token' => 9,
'raw' => 'get_key()[1] => &$e',
- 'is_empty' => false,
'assignment' => '$e',
- 'nested_list' => false,
- 'assign_by_reference' => true,
- 'reference_token' => 11,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$e',
'assignment_token' => 12,
'assignment_end_token' => 12,
+ 'assign_by_reference' => true,
+ 'reference_token' => 11,
+ 'key' => 'get_key()[1]',
+ 'key_token' => 2,
+ 'key_end_token' => 7,
+ 'double_arrow_token' => 9,
],
],
],
@@ -699,25 +776,25 @@ public function dataGetAssignments()
[
0 => [
'raw' => '${$drink}',
- 'is_empty' => false,
'assignment' => '${$drink}',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => false,
'assignment_token' => 3,
'assignment_end_token' => 6,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
1 => [
'raw' => '$foo->{$bar[\'baz\']}',
- 'is_empty' => false,
'assignment' => '$foo->{$bar[\'baz\']}',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$foo',
'assignment_token' => 9,
'assignment_end_token' => 16,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
],
],
@@ -726,36 +803,36 @@ public function dataGetAssignments()
\T_LIST,
[
0 => [
- 'key' => "'a'",
- 'key_token' => 4,
- 'key_end_token' => 4,
- 'double_arrow_token' => 6,
'raw' => '\'a\' =>
list("x" => $x1, "y" => $y1)',
- 'is_empty' => false,
'assignment' => 'list("x" => $x1, "y" => $y1)',
- 'nested_list' => true,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => true,
'variable' => false,
'assignment_token' => 9,
'assignment_end_token' => 23,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => "'a'",
+ 'key_token' => 4,
+ 'key_end_token' => 4,
+ 'double_arrow_token' => 6,
],
1 => [
- 'key' => "'b'",
- 'key_token' => 27,
- 'key_end_token' => 27,
- 'double_arrow_token' => 29,
'raw' => '\'b\' =>
list("x" => $x2, "y" => $y2)',
- 'is_empty' => false,
'assignment' => 'list("x" => $x2, "y" => $y2)',
- 'nested_list' => true,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => true,
'variable' => false,
'assignment_token' => 32,
'assignment_end_token' => 46,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => "'b'",
+ 'key_token' => 27,
+ 'key_end_token' => 27,
+ 'double_arrow_token' => 29,
],
],
],
@@ -765,29 +842,29 @@ public function dataGetAssignments()
[
0 => [
'raw' => '$unkeyed',
- 'is_empty' => false,
'assignment' => '$unkeyed',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$unkeyed',
'assignment_token' => 2,
'assignment_end_token' => 2,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
1 => [
- 'key' => '"key"',
- 'key_token' => 5,
- 'key_end_token' => 5,
- 'double_arrow_token' => 7,
'raw' => '"key" => $keyed',
- 'is_empty' => false,
'assignment' => '$keyed',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$keyed',
'assignment_token' => 9,
'assignment_end_token' => 9,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => '"key"',
+ 'key_token' => 5,
+ 'key_end_token' => 5,
+ 'double_arrow_token' => 7,
],
],
],
@@ -797,34 +874,62 @@ public function dataGetAssignments()
[
0 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
1 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
2 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
3 => [
'raw' => '',
+ 'assignment' => '',
'is_empty' => true,
+ 'is_nested_list' => false,
+ 'variable' => false,
+ 'assignment_token' => false,
+ 'assignment_end_token' => false,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
],
4 => [
- 'key' => '"key"',
- 'key_token' => 6,
- 'key_end_token' => 6,
- 'double_arrow_token' => 8,
'raw' => '"key" => $keyed',
- 'is_empty' => false,
'assignment' => '$keyed',
- 'nested_list' => false,
- 'assign_by_reference' => false,
- 'reference_token' => false,
+ 'is_empty' => false,
+ 'is_nested_list' => false,
'variable' => '$keyed',
'assignment_token' => 10,
'assignment_end_token' => 10,
+ 'assign_by_reference' => false,
+ 'reference_token' => false,
+ 'key' => '"key"',
+ 'key_token' => 6,
+ 'key_end_token' => 6,
+ 'double_arrow_token' => 8,
],
],
],
From 59739e89c6e700d7b28358e5b3f3f5d3d879374f Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Mon, 6 Apr 2020 13:26:07 +0200
Subject: [PATCH 38/79] Tokens\Collections: add two new properties
* `$incrementDecrementOperators` containing the tokens for the increment and decrement operators.
* `$objectOperators` containing the tokens used as object operators.
---
PHPCSUtils/Tokens/Collections.php | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/PHPCSUtils/Tokens/Collections.php b/PHPCSUtils/Tokens/Collections.php
index a192e0e3..1df8dcee 100644
--- a/PHPCSUtils/Tokens/Collections.php
+++ b/PHPCSUtils/Tokens/Collections.php
@@ -150,6 +150,18 @@ class Collections
\T_DECLARE => \T_DECLARE,
];
+ /**
+ * Increment/decrement operator tokens.
+ *
+ * @since 1.0.0-alpha3
+ *
+ * @var array =>
+ */
+ public static $incrementDecrementOperators = [
+ \T_DEC => \T_DEC,
+ \T_INC => \T_INC,
+ ];
+
/**
* Tokens which are used to create lists.
*
@@ -220,6 +232,18 @@ class Collections
\T_CLOSE_TAG => \T_CLOSE_TAG,
];
+ /**
+ * Object operators.
+ *
+ * @since 1.0.0-alpha3
+ *
+ * @var array =>
+ */
+ public static $objectOperators = [
+ \T_OBJECT_OPERATOR => \T_OBJECT_OPERATOR,
+ \T_DOUBLE_COLON => \T_DOUBLE_COLON,
+ ];
+
/**
* OO structures which can use the `extends` keyword.
*
From 614972aa5343589e335bebc43dddf1f3718ee3fa Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Sun, 26 Apr 2020 07:16:43 +0200
Subject: [PATCH 39/79] ObjectDeclarations::getName(): bug fix for functions
returning by reference
Includes unit tests.
---
PHPCSUtils/Utils/ObjectDeclarations.php | 1 +
Tests/BackCompat/BCFile/GetDeclarationNameTest.inc | 6 ++++++
Tests/BackCompat/BCFile/GetDeclarationNameTest.php | 8 ++++++++
3 files changed, 15 insertions(+)
diff --git a/PHPCSUtils/Utils/ObjectDeclarations.php b/PHPCSUtils/Utils/ObjectDeclarations.php
index a154e502..8f38fb7e 100644
--- a/PHPCSUtils/Utils/ObjectDeclarations.php
+++ b/PHPCSUtils/Utils/ObjectDeclarations.php
@@ -120,6 +120,7 @@ public static function getName(File $phpcsFile, $stackPtr)
$exclude = Tokens::$emptyTokens;
$exclude[] = \T_OPEN_PARENTHESIS;
$exclude[] = \T_OPEN_CURLY_BRACKET;
+ $exclude[] = \T_BITWISE_AND;
$nameStart = $phpcsFile->findNext($exclude, ($stackPtr + 1), $stopPoint, true);
if ($nameStart === false) {
diff --git a/Tests/BackCompat/BCFile/GetDeclarationNameTest.inc b/Tests/BackCompat/BCFile/GetDeclarationNameTest.inc
index 466f07df..fcb3ad05 100644
--- a/Tests/BackCompat/BCFile/GetDeclarationNameTest.inc
+++ b/Tests/BackCompat/BCFile/GetDeclarationNameTest.inc
@@ -27,6 +27,9 @@ $class = new class extends SomeClass {
/* testFunction */
function functionName() {}
+/* testFunctionReturnByRef */
+function & functionNameByRef();
+
/* testClass */
abstract class ClassName {
/* testMethod */
@@ -34,6 +37,9 @@ abstract class ClassName {
/* testAbstractMethod */
abstract protected function abstractMethodName();
+
+ /* testMethodReturnByRef */
+ private function &MethodNameByRef();
}
/* testExtendedClass */
diff --git a/Tests/BackCompat/BCFile/GetDeclarationNameTest.php b/Tests/BackCompat/BCFile/GetDeclarationNameTest.php
index e8a5cb37..95aa9981 100644
--- a/Tests/BackCompat/BCFile/GetDeclarationNameTest.php
+++ b/Tests/BackCompat/BCFile/GetDeclarationNameTest.php
@@ -133,6 +133,10 @@ public function dataGetDeclarationName()
'/* testFunction */',
'functionName',
],
+ 'function-return-by-reference' => [
+ '/* testFunctionReturnByRef */',
+ 'functionNameByRef',
+ ],
'class' => [
'/* testClass */',
'ClassName',
@@ -145,6 +149,10 @@ public function dataGetDeclarationName()
'/* testAbstractMethod */',
'abstractMethodName',
],
+ 'method-return-by-reference' => [
+ '/* testMethodReturnByRef */',
+ 'MethodNameByRef',
+ ],
'extended-class' => [
'/* testExtendedClass */',
'ExtendedClass',
From 49324bbabb37cf8f90e4ccd42948b09a98ccc230 Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Fri, 1 May 2020 02:35:26 +0200
Subject: [PATCH 40/79] Namespaces::getType(): improve detection of namespace
keyword as operator
When the `namespace` keyword is used in the global namespace, it is less easy to determine with 100% certainty whether it used for a declaration or as an operator, especially when taking into account potential coding errors, such as the use of reserved keywords in the namespace name and/or a leading backslash used in a namespace declaration.
This PR hardens the code and will more often correctly detect whether the namespace keyword is used as an operator when used in the global namespace.
Includes unit tests.
---
PHPCSUtils/Utils/Namespaces.php | 26 ++++++++++++++++--
Tests/Utils/Namespaces/NamespaceTypeTest.inc | 19 ++++++++++---
Tests/Utils/Namespaces/NamespaceTypeTest.php | 28 ++++++++++++++++++++
3 files changed, 68 insertions(+), 5 deletions(-)
diff --git a/PHPCSUtils/Utils/Namespaces.php b/PHPCSUtils/Utils/Namespaces.php
index 68009e3a..34b0d27c 100644
--- a/PHPCSUtils/Utils/Namespaces.php
+++ b/PHPCSUtils/Utils/Namespaces.php
@@ -14,6 +14,7 @@
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\BackCompat\BCFile;
+use PHPCSUtils\BackCompat\BCTokens;
use PHPCSUtils\Tokens\Collections;
use PHPCSUtils\Utils\Conditions;
use PHPCSUtils\Utils\GetTokensAsString;
@@ -46,6 +47,26 @@ class Namespaces
*/
public static function getType(File $phpcsFile, $stackPtr)
{
+ static $findAfter;
+
+ if (isset($findAfter) === false) {
+ /*
+ * Set up array of tokens which can only be used in combination with the keyword as operator
+ * and which cannot be confused with other keywords.
+ */
+ $findAfter = BCTokens::assignmentTokens()
+ + BCTokens::comparisonTokens()
+ + BCTokens::operators()
+ + Tokens::$castTokens
+ + Tokens::$blockOpeners
+ + Collections::$incrementDecrementOperators
+ + Collections::$objectOperators;
+
+ $findAfter[\T_OPEN_CURLY_BRACKET] = \T_OPEN_CURLY_BRACKET;
+ $findAfter[\T_OPEN_SQUARE_BRACKET] = \T_OPEN_SQUARE_BRACKET;
+ $findAfter[\T_OPEN_SHORT_ARRAY] = \T_OPEN_SHORT_ARRAY;
+ }
+
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_NAMESPACE) {
@@ -80,8 +101,9 @@ public static function getType(File $phpcsFile, $stackPtr)
return 'declaration';
}
- if ($start !== $stackPtr
- && $tokens[$next]['code'] === \T_NS_SEPARATOR
+ if ($tokens[$next]['code'] === \T_NS_SEPARATOR
+ && ($start !== $stackPtr
+ || $phpcsFile->findNext($findAfter, ($stackPtr + 1), null, false, null, true) !== false)
) {
return 'operator';
}
diff --git a/Tests/Utils/Namespaces/NamespaceTypeTest.inc b/Tests/Utils/Namespaces/NamespaceTypeTest.inc
index f2927f8e..99e13c1a 100644
--- a/Tests/Utils/Namespaces/NamespaceTypeTest.inc
+++ b/Tests/Utils/Namespaces/NamespaceTypeTest.inc
@@ -28,11 +28,24 @@ function closedScope() {
echo namespace\ClassName::method();
while( true ) {
- /* testNamespaceOperatorInParentheses */
- function_call( namespace\ClassName::$property );
- }
+ /* testNamespaceOperatorInParentheses */
+ function_call( namespace\ClassName::$property );
+ }
}
+/* testNamespaceOperatorGlobalNamespaceStartOfStatementFunctionCall */
+namespace\functionCall();
+
+/* testNamespaceOperatorGlobalNamespaceStartOfStatementCombiWithNonConfusingToken1 */
+namespace\CONSTANT === 'test' or die();
+
+/* testNamespaceOperatorGlobalNamespaceStartOfStatementCombiWithNonConfusingToken2 */
+namespace\ClassName::$property++;
+
+/* testNamespaceOperatorGlobalNamespaceStartOfStatementCombiWithNonConfusingToken3 */
+namespace\CONSTANT['key'];
+
+
/* testParseErrorScopedNamespaceDeclaration */
function testScope() {
namespace My\Namespace;
diff --git a/Tests/Utils/Namespaces/NamespaceTypeTest.php b/Tests/Utils/Namespaces/NamespaceTypeTest.php
index 23dffcdf..5eb39394 100644
--- a/Tests/Utils/Namespaces/NamespaceTypeTest.php
+++ b/Tests/Utils/Namespaces/NamespaceTypeTest.php
@@ -156,6 +156,34 @@ public function dataNamespaceType()
'operator' => true,
],
],
+ 'namespace-operator-global-namespace-start-of-statement-function-call' => [
+ '/* testNamespaceOperatorGlobalNamespaceStartOfStatementFunctionCall */',
+ [
+ 'declaration' => false,
+ 'operator' => true,
+ ],
+ ],
+ 'namespace-operator-global-namespace-start-of-statement-with-non-confusing-token-1' => [
+ '/* testNamespaceOperatorGlobalNamespaceStartOfStatementCombiWithNonConfusingToken1 */',
+ [
+ 'declaration' => false,
+ 'operator' => true,
+ ],
+ ],
+ 'namespace-operator-global-namespace-start-of-statement-with-non-confusing-token-2' => [
+ '/* testNamespaceOperatorGlobalNamespaceStartOfStatementCombiWithNonConfusingToken2 */',
+ [
+ 'declaration' => false,
+ 'operator' => true,
+ ],
+ ],
+ 'namespace-operator-global-namespace-start-of-statement-with-non-confusing-token-3' => [
+ '/* testNamespaceOperatorGlobalNamespaceStartOfStatementCombiWithNonConfusingToken3 */',
+ [
+ 'declaration' => false,
+ 'operator' => true,
+ ],
+ ],
'parse-error-scoped-namespace-declaration' => [
'/* testParseErrorScopedNamespaceDeclaration */',
[
From c4692ca36664ad12c878bbb2dbf622b7a3867dc6 Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Sun, 3 May 2020 17:34:28 +0200
Subject: [PATCH 41/79] Tokens\Collections: add two new methods
* `functionDeclarationTokens()` returning the tokens which can represent a keyword which starts a function declaration.
* `functionDeclarationTokensBC()` same, but allowing for BC down to PHPCS 2.6.0 (arrow functions).
Includes unit tests.
---
PHPCSUtils/Tokens/Collections.php | 66 +++++++++++++++++++
.../FunctionDeclarationTokensBCTest.php | 51 ++++++++++++++
.../FunctionDeclarationTokensTest.php | 50 ++++++++++++++
3 files changed, 167 insertions(+)
create mode 100644 Tests/Tokens/Collections/FunctionDeclarationTokensBCTest.php
create mode 100644 Tests/Tokens/Collections/FunctionDeclarationTokensTest.php
diff --git a/PHPCSUtils/Tokens/Collections.php b/PHPCSUtils/Tokens/Collections.php
index 1df8dcee..d9d0bbe9 100644
--- a/PHPCSUtils/Tokens/Collections.php
+++ b/PHPCSUtils/Tokens/Collections.php
@@ -531,6 +531,72 @@ public static function arrowFunctionTokensBC()
return $tokens;
}
+ /**
+ * Tokens which can represent a keyword which starts a function declaration.
+ *
+ * Note: this is a method, not a property as the `T_FN` token may not exist.
+ *
+ * Sister-method to the `functionDeclarationTokensBC()` method.
+ * This method supports PHPCS 3.5.3 and up.
+ * The `functionDeclarationTokensBC()` method supports PHPCS 2.6.0 and up.
+ *
+ * @see \PHPCSUtils\Tokens\Collections::functionDeclarationTokensBC() Related method (PHPCS 2.6.0+).
+ *
+ * @since 1.0.0
+ *
+ * @return array =>
+ */
+ public static function functionDeclarationTokens()
+ {
+ $tokens = [
+ \T_FUNCTION => \T_FUNCTION,
+ \T_CLOSURE => \T_CLOSURE,
+ ];
+
+ if (\defined('T_FN') === true) {
+ // PHP 7.4 or PHPCS 3.5.3+.
+ $tokens[\T_FN] = \T_FN;
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Tokens which can represent a keyword which starts a function declaration.
+ *
+ * Note: this is a method, not a property as the `T_FN` token may not exist.
+ *
+ * Sister-method to the `functionDeclarationTokens()` method.
+ * The `functionDeclarationTokens()` method supports PHPCS 3.5.3 and up.
+ * This method supports PHPCS 2.6.0 and up.
+ *
+ * Notable difference:
+ * This method accounts for when the `T_FN` token doesn't exist.
+ * Note: if this method is used, the `FunctionDeclarations::isArrowFunction() method
+ * needs to be used on arrow function tokens to verify whether it really is an arrow function
+ * declaration or not.
+ *
+ * It is recommended to use the method instead of the property if a standard supports PHPCS < 3.3.0.
+ *
+ * @see \PHPCSUtils\Tokens\Collections::functionDeclarationTokens() Related method (PHPCS 3.5.3+).
+ * @see \PHPCSUtils\Tokens\FunctionDeclarations::isArrowFunction() Arrow function verification.
+ *
+ * @since 1.0.0
+ *
+ * @return array =>
+ */
+ public static function functionDeclarationTokensBC()
+ {
+ $tokens = [
+ \T_FUNCTION => \T_FUNCTION,
+ \T_CLOSURE => \T_CLOSURE,
+ ];
+
+ $tokens += self::arrowFunctionTokensBC();
+
+ return $tokens;
+ }
+
/**
* Token types which can be encountered in a parameter type declaration (cross-version).
*
diff --git a/Tests/Tokens/Collections/FunctionDeclarationTokensBCTest.php b/Tests/Tokens/Collections/FunctionDeclarationTokensBCTest.php
new file mode 100644
index 00000000..4a40ecfc
--- /dev/null
+++ b/Tests/Tokens/Collections/FunctionDeclarationTokensBCTest.php
@@ -0,0 +1,51 @@
+ \T_FUNCTION,
+ \T_CLOSURE => \T_CLOSURE,
+ \T_STRING => \T_STRING,
+ ];
+
+ if (\version_compare($version, '3.5.3', '>=') === true
+ || \version_compare(\PHP_VERSION_ID, '70399', '>=') === true
+ ) {
+ $expected[\T_FN] = \T_FN;
+ }
+
+ $this->assertSame($expected, Collections::functionDeclarationTokensBC());
+ }
+}
diff --git a/Tests/Tokens/Collections/FunctionDeclarationTokensTest.php b/Tests/Tokens/Collections/FunctionDeclarationTokensTest.php
new file mode 100644
index 00000000..e5e2c3cd
--- /dev/null
+++ b/Tests/Tokens/Collections/FunctionDeclarationTokensTest.php
@@ -0,0 +1,50 @@
+ \T_FUNCTION,
+ \T_CLOSURE => \T_CLOSURE,
+ ];
+
+ if (\version_compare($version, '3.5.3', '>=') === true
+ || \version_compare(\PHP_VERSION_ID, '70399', '>=') === true
+ ) {
+ $expected[\T_FN] = \T_FN;
+ }
+
+ $this->assertSame($expected, Collections::functionDeclarationTokens());
+ }
+}
From eb061fda8fc22d063aa3c1fda414e046b4f2b9f1 Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Mon, 4 May 2020 01:15:08 +0200
Subject: [PATCH 42/79] Tokens\Collections::$returnTypeTokens: allow for
"static" (PHP 8)
As of PHP 8.0, `static` can be used as a return type for function declarations.
Ref: https://wiki.php.net/rfc/static_return_type
Includes adding a unit test for the `BCFile::getMethodProperties()`/`FunctionDeclarations::getProperties()` methods to safeguard this.
Sister-PR to the upstream squizlabs/PHP_CodeSniffer 2952
---
PHPCSUtils/Tokens/Collections.php | 1 +
.../BCFile/GetMethodPropertiesTest.inc | 7 ++++++
.../BCFile/GetMethodPropertiesTest.php | 22 +++++++++++++++++++
3 files changed, 30 insertions(+)
diff --git a/PHPCSUtils/Tokens/Collections.php b/PHPCSUtils/Tokens/Collections.php
index d9d0bbe9..2c5dc19c 100644
--- a/PHPCSUtils/Tokens/Collections.php
+++ b/PHPCSUtils/Tokens/Collections.php
@@ -423,6 +423,7 @@ class Collections
\T_CALLABLE => \T_CALLABLE,
\T_SELF => \T_SELF,
\T_PARENT => \T_PARENT,
+ \T_STATIC => \T_STATIC,
\T_NS_SEPARATOR => \T_NS_SEPARATOR,
];
diff --git a/Tests/BackCompat/BCFile/GetMethodPropertiesTest.inc b/Tests/BackCompat/BCFile/GetMethodPropertiesTest.inc
index f21ecdb3..106d65b1 100644
--- a/Tests/BackCompat/BCFile/GetMethodPropertiesTest.inc
+++ b/Tests/BackCompat/BCFile/GetMethodPropertiesTest.inc
@@ -67,6 +67,13 @@ $result = array_map(
$numbers
);
+class ReturnMe {
+ /* testReturnTypeStatic */
+ private function myFunction(): static {
+ return $this;
+ }
+}
+
/* testNotAFunction */
return true;
diff --git a/Tests/BackCompat/BCFile/GetMethodPropertiesTest.php b/Tests/BackCompat/BCFile/GetMethodPropertiesTest.php
index 97649bc3..8ad64dc4 100644
--- a/Tests/BackCompat/BCFile/GetMethodPropertiesTest.php
+++ b/Tests/BackCompat/BCFile/GetMethodPropertiesTest.php
@@ -433,6 +433,28 @@ public function testArrowFunction()
$this->getMethodPropertiesTestHelper('/* ' . __FUNCTION__ . ' */', $expected, $arrowTokenTypes);
}
+ /**
+ * Test a function with return type "static".
+ *
+ * @return void
+ */
+ public function testReturnTypeStatic()
+ {
+ $expected = [
+ 'scope' => 'private',
+ 'scope_specified' => true,
+ 'return_type' => 'static',
+ 'return_type_token' => 7, // Offset from the T_FUNCTION token.
+ 'nullable_return_type' => false,
+ 'is_abstract' => false,
+ 'is_final' => false,
+ 'is_static' => false,
+ 'has_body' => true,
+ ];
+
+ $this->getMethodPropertiesTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
+ }
+
/**
* Test for incorrect tokenization of array return type declarations in PHPCS < 2.8.0.
*
From fa703bd8f1d824ce5041e10ac220d9cb5faaf691 Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Tue, 5 May 2020 23:55:05 +0200
Subject: [PATCH 43/79] Composer: set minimum Composer PHPCS plugin dependency
version to `0.4.1`
Support for the `installed_paths` being set when the plugin is a requirement of a standard itself, was only added in version `0.4.0` and a pertinent bug in this feature was fixed in `0.4.1`, which effectively makes version `0.4.1` the minimum workable version for both use as a stand-alone standard, as well as when this standard is required as a dependency.
Refs:
* https://github.com/Dealerdirect/phpcodesniffer-composer-installer/releases/tag/v0.4.0
* https://github.com/Dealerdirect/phpcodesniffer-composer-installer/releases/tag/v0.4.1
---
composer.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/composer.json b/composer.json
index 978c3960..e077ce88 100644
--- a/composer.json
+++ b/composer.json
@@ -24,7 +24,7 @@
"require" : {
"php" : ">=5.4",
"squizlabs/php_codesniffer" : "^2.6.0 || ^3.1.0 || 4.0.x-dev@dev",
- "dealerdirect/phpcodesniffer-composer-installer" : "^0.3 || ^0.4.1 || ^0.5 || ^0.6.2"
+ "dealerdirect/phpcodesniffer-composer-installer" : "^0.4.1 || ^0.5 || ^0.6.2"
},
"require-dev" : {
"php-parallel-lint/php-parallel-lint": "^1.1.0",
From 5a0379adb8e18304b25c412348d89867edb2d93f Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Tue, 12 May 2020 12:49:12 +0200
Subject: [PATCH 44/79] TextStrings::getCompleteTextString(): remove newline at
end of heredoc/nowdoc
PHP itself does not include the last new line in a heredoc/nowdoc text string when handling it, so this method shouldn't either.
Included adjusted unit tests.
---
PHPCSUtils/Utils/TextStrings.php | 21 +++++++++++++------
.../TextStrings/GetCompleteTextStringTest.php | 12 ++++-------
2 files changed, 19 insertions(+), 14 deletions(-)
diff --git a/PHPCSUtils/Utils/TextStrings.php b/PHPCSUtils/Utils/TextStrings.php
index 5b2a46e2..3c314e36 100644
--- a/PHPCSUtils/Utils/TextStrings.php
+++ b/PHPCSUtils/Utils/TextStrings.php
@@ -62,17 +62,21 @@ public static function getCompleteTextString(File $phpcsFile, $stackPtr, $stripQ
}
}
+ $stripNewline = false;
+
switch ($tokens[$stackPtr]['code']) {
case \T_START_HEREDOC:
- $stripQuotes = false;
- $targetType = \T_HEREDOC;
- $current = ($stackPtr + 1);
+ $stripQuotes = false;
+ $stripNewline = true;
+ $targetType = \T_HEREDOC;
+ $current = ($stackPtr + 1);
break;
case \T_START_NOWDOC:
- $stripQuotes = false;
- $targetType = \T_NOWDOC;
- $current = ($stackPtr + 1);
+ $stripQuotes = false;
+ $stripNewline = true;
+ $targetType = \T_NOWDOC;
+ $current = ($stackPtr + 1);
break;
default:
@@ -87,6 +91,11 @@ public static function getCompleteTextString(File $phpcsFile, $stackPtr, $stripQ
++$current;
} while (isset($tokens[$current]) && $tokens[$current]['code'] === $targetType);
+ if ($stripNewline === true) {
+ // Heredoc/nowdoc: strip the new line at the end of the string to emulate how PHP sees the string.
+ $string = rtrim($string, "\r\n");
+ }
+
if ($stripQuotes === true) {
return self::stripQuotes($string);
}
diff --git a/Tests/Utils/TextStrings/GetCompleteTextStringTest.php b/Tests/Utils/TextStrings/GetCompleteTextStringTest.php
index f31193bd..0a8bc022 100644
--- a/Tests/Utils/TextStrings/GetCompleteTextStringTest.php
+++ b/Tests/Utils/TextStrings/GetCompleteTextStringTest.php
@@ -156,26 +156,22 @@ public function dataGetCompleteTextString()
'first line
second $line
third line
-fourth line
-',
+fourth line',
'first line
second $line
third line
-fourth line
-',
+fourth line',
],
'nowdoc' => [
'/* testNowdocString */',
'first line
second line
third line
-fourth line
-',
+fourth line',
'first line
second line
third line
-fourth line
-',
+fourth line',
],
'text-string-at-end-of-file' => [
'/* testTextStringAtEndOfFile */',
From 2ffa09baf333435969ede40aa4619af33efdcc89 Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Fri, 15 May 2020 01:02:38 +0200
Subject: [PATCH 45/79] Helper::setConfigData(): PHPCS 4.x compatibility
As of PHPCS 4.x, the `PHP_CodeSniffer\Config::setConfigData()` method is no longer static.
Ref: https://github.com/squizlabs/PHP_CodeSniffer/commit/10a89a2c8c33a26466ccb3368a69a5e99549f80e
This commit adds a new `$config` parameter to the `Helper::setConfigData()` method which will be a required parameter for PHPCS 4.x.
Includes adjusted unit tests.
---
PHPCSUtils/BackCompat/Helper.php | 27 ++++++--
Tests/BackCompat/Helper/ConfigDataTest.php | 62 ++++++++++++++++++-
.../Helper/GetCommandLineDataTest.php | 20 ++++--
3 files changed, 95 insertions(+), 14 deletions(-)
diff --git a/PHPCSUtils/BackCompat/Helper.php b/PHPCSUtils/BackCompat/Helper.php
index 7a122105..aeb0cf5a 100644
--- a/PHPCSUtils/BackCompat/Helper.php
+++ b/PHPCSUtils/BackCompat/Helper.php
@@ -11,6 +11,7 @@
namespace PHPCSUtils\BackCompat;
use PHP_CodeSniffer\Files\File;
+use PHP_CodeSniffer\Exceptions\RuntimeException;
/**
* Utility methods to retrieve (configuration) information from PHP_CodeSniffer.
@@ -60,21 +61,35 @@ public static function getVersion()
*
* @since 1.0.0
*
- * @param string $key The name of the config value.
- * @param string|null $value The value to set. If null, the config entry
- * is deleted, reverting it to the default value.
- * @param bool $temp Set this config data temporarily for this script run.
- * This will not write the config data to the config file.
+ * @param string $key The name of the config value.
+ * @param string|null $value The value to set. If null, the config entry
+ * is deleted, reverting it to the default value.
+ * @param bool $temp Set this config data temporarily for this script run.
+ * This will not write the config data to the config file.
+ * @param \PHP_CodeSniffer\Config $config The PHPCS config object.
+ * This parameter is required for PHPCS 4.x, optional
+ * for PHPCS 3.x and not possible to pass for PHPCS 2.x.
+ * Passing the `$phpcsFile->config` property should work
+ * in PHPCS 3.x and higher.
*
* @return bool Whether the setting of the data was successfull.
*/
- public static function setConfigData($key, $value, $temp = false)
+ public static function setConfigData($key, $value, $temp = false, $config = null)
{
if (\method_exists('\PHP_CodeSniffer\Config', 'setConfigData') === false) {
// PHPCS 2.x.
return \PHP_CodeSniffer::setConfigData($key, $value, $temp);
}
+ if (isset($config) === true) {
+ // PHPCS 3.x and 4.x.
+ return $config->setConfigData($key, $value, $temp);
+ }
+
+ if (version_compare(self::getVersion(), '3.99.99', '>') === true) {
+ throw new RuntimeException('Passing the $config parameter is required in PHPCS 4.x');
+ }
+
// PHPCS 3.x.
return \PHP_CodeSniffer\Config::setConfigData($key, $value, $temp);
}
diff --git a/Tests/BackCompat/Helper/ConfigDataTest.php b/Tests/BackCompat/Helper/ConfigDataTest.php
index f570e0f2..9cd2a74e 100644
--- a/Tests/BackCompat/Helper/ConfigDataTest.php
+++ b/Tests/BackCompat/Helper/ConfigDataTest.php
@@ -10,6 +10,7 @@
namespace PHPCSUtils\Tests\BackCompat\Helper;
+use PHP_CodeSniffer\Config;
use PHPCSUtils\BackCompat\Helper;
use PHPUnit\Framework\TestCase;
@@ -27,12 +28,41 @@ class ConfigDataTest extends TestCase
{
/**
- * Test the getConfigData() and setConfigData() method.
+ * Test the getConfigData() and setConfigData() method when used in a cross-version compatible manner.
*
* @return void
*/
- public function testConfigData()
+ public function testConfigData34()
{
+ if (version_compare(Helper::getVersion(), '2.99.99', '<=') === true) {
+ $this->markTestSkipped('Test only applicable to PHPCS > 2.x');
+ }
+
+ $config = new Config();
+ $original = Helper::getConfigData('arbitrary_name');
+ $expected = 'expected';
+
+ $return = Helper::setConfigData('arbitrary_name', $expected, true, $config);
+ $this->assertTrue($return);
+
+ $result = Helper::getConfigData('arbitrary_name');
+ $this->assertSame($expected, $result);
+
+ // Reset the value after the test.
+ Helper::setConfigData('arbitrary_name', $original, true, $config);
+ }
+
+ /**
+ * Test the getConfigData() and setConfigData() method when used in a non-PHPCS 4.x compatible manner.
+ *
+ * @return void
+ */
+ public function testConfigDataPHPCS23()
+ {
+ if (version_compare(Helper::getVersion(), '3.99.99', '>') === true) {
+ $this->markTestSkipped('Test only applicable to PHPCS < 4.x');
+ }
+
$original = Helper::getConfigData('arbitrary_name');
$expected = 'expected';
@@ -43,6 +73,32 @@ public function testConfigData()
$this->assertSame($expected, $result);
// Reset the value after the test.
- $return = Helper::setConfigData('arbitrary_name', $original, true);
+ Helper::setConfigData('arbitrary_name', $original, true);
+ }
+
+ /**
+ * Test the getConfigData() and setConfigData() method when used in a non-PHPCS 4.x compatible manner.
+ *
+ * @return void
+ */
+ public function testConfigDataPHPCS4Exception()
+ {
+ if (version_compare(Helper::getVersion(), '3.99.99', '<=') === true) {
+ $this->markTestSkipped('Test only applicable to PHPCS 4.x');
+ }
+
+ $msg = 'Passing the $config parameter is required in PHPCS 4.x';
+ $exception = 'PHP_CodeSniffer\Exceptions\RuntimeException';
+
+ if (\method_exists($this, 'expectException')) {
+ // PHPUnit 5+.
+ $this->expectException($exception);
+ $this->expectExceptionMessage($msg);
+ } else {
+ // PHPUnit 4.
+ $this->setExpectedException($exception, $msg);
+ }
+
+ Helper::setConfigData('arbitrary_name', 'test', true);
}
}
diff --git a/Tests/BackCompat/Helper/GetCommandLineDataTest.php b/Tests/BackCompat/Helper/GetCommandLineDataTest.php
index 7ce9c868..9c143091 100644
--- a/Tests/BackCompat/Helper/GetCommandLineDataTest.php
+++ b/Tests/BackCompat/Helper/GetCommandLineDataTest.php
@@ -132,16 +132,21 @@ public function testGetEncodingWithoutPHPCSFile()
$expected = \version_compare(static::$phpcsVersion, '2.99.99', '>') ? 'utf-8' : 'iso-8859-1';
$this->assertSame($expected, $result, 'Failed retrieving the default encoding');
- Helper::setConfigData('encoding', 'utf-16', true);
+ $config = null;
+ if (isset(self::$phpcsFile->config) === true) {
+ $config = self::$phpcsFile->config;
+ }
+
+ Helper::setConfigData('encoding', 'utf-16', true, $config);
$result = Helper::getEncoding();
$this->assertSame('utf-16', $result, 'Failed retrieving the custom set encoding');
// Restore defaults before moving to the next test.
if (\version_compare(static::$phpcsVersion, '2.99.99', '>') === true) {
- Helper::setConfigData('encoding', 'utf-8', true);
+ Helper::setConfigData('encoding', 'utf-8', true, $config);
} else {
- Helper::setConfigData('encoding', 'iso-8859-1', true);
+ Helper::setConfigData('encoding', 'iso-8859-1', true, $config);
}
}
@@ -197,13 +202,18 @@ public function testIgnoreAnnotationsV3SetViaMethod()
$this->markTestSkipped('Test only applicable to PHPCS 3.x');
}
- Helper::setConfigData('annotations', false, true);
+ $config = null;
+ if (isset(self::$phpcsFile->config) === true) {
+ $config = self::$phpcsFile->config;
+ }
+
+ Helper::setConfigData('annotations', false, true, $config);
$result = Helper::ignoreAnnotations();
$this->assertTrue($result);
// Restore defaults before moving to the next test.
- Helper::setConfigData('annotations', true, true);
+ Helper::setConfigData('annotations', true, true, $config);
}
/**
From 6593f61c5e7ad7c582c00903d52284c22af834f1 Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Sun, 17 May 2020 01:42:00 +0200
Subject: [PATCH 46/79] ControlStructures::getCaughtExceptions(): allow for
PHP8 non-capturing catch
PHP 8 will introduce non-capturing catch statements.
This tiny fix makes sure the method can handle these correctly.
Includes unit test.
Ref: https://wiki.php.net/rfc/non-capturing_catches
---
PHPCSUtils/Utils/ControlStructures.php | 2 +-
.../ControlStructures/GetCaughtExceptionsTest.inc | 3 +++
.../ControlStructures/GetCaughtExceptionsTest.php | 15 +++++++++++++++
3 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/PHPCSUtils/Utils/ControlStructures.php b/PHPCSUtils/Utils/ControlStructures.php
index 892987db..f2a53a9e 100644
--- a/PHPCSUtils/Utils/ControlStructures.php
+++ b/PHPCSUtils/Utils/ControlStructures.php
@@ -383,7 +383,7 @@ public static function getCaughtExceptions(File $phpcsFile, $stackPtr)
$firstToken = null;
$lastToken = null;
- for ($i = ($opener + 1); $i < $closer; $i++) {
+ for ($i = ($opener + 1); $i <= $closer; $i++) {
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']])) {
continue;
}
diff --git a/Tests/Utils/ControlStructures/GetCaughtExceptionsTest.inc b/Tests/Utils/ControlStructures/GetCaughtExceptionsTest.inc
index b1b45855..aee219ad 100644
--- a/Tests/Utils/ControlStructures/GetCaughtExceptionsTest.inc
+++ b/Tests/Utils/ControlStructures/GetCaughtExceptionsTest.inc
@@ -27,6 +27,9 @@ try {
/* testMultiCatchCompoundNames */
} catch (\NS\RuntimeException | My\ParseErrorException | namespace \ AnotherException $e) {
+/* testPHP8NonCapturingCatch */
+} catch (RuntimeException | AnotherException) {
+
/* testLiveCoding */
// Intentional parse error.
} catch (
diff --git a/Tests/Utils/ControlStructures/GetCaughtExceptionsTest.php b/Tests/Utils/ControlStructures/GetCaughtExceptionsTest.php
index d52c8074..0c7811a5 100644
--- a/Tests/Utils/ControlStructures/GetCaughtExceptionsTest.php
+++ b/Tests/Utils/ControlStructures/GetCaughtExceptionsTest.php
@@ -197,6 +197,21 @@ public function dataGetCaughtExceptions()
],
],
],
+ 'non-capturing-catch' => [
+ 'target' => '/* testPHP8NonCapturingCatch */',
+ 'expected' => [
+ [
+ 'type' => 'RuntimeException',
+ 'type_token' => 3,
+ 'type_end_token' => 3,
+ ],
+ [
+ 'type' => 'AnotherException',
+ 'type_token' => 7,
+ 'type_end_token' => 7,
+ ],
+ ],
+ ],
];
}
}
From 8eefd10233ffc963b6b8ca259a8d3c00b37cf200 Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Thu, 28 May 2020 21:03:13 +0200
Subject: [PATCH 47/79] BCTokens: change property visibility
:warning: Breaking Change :warning:
The `$phpcsCommentTokensTypes`, `$ooScopeTokens`, `$textStringTokens` properties all had `protected` visibility.
These properties are not intended to be used directly by implementing standards and with them being `protected`, they would only be accessible if the implementing standard would extend the `BCTokens` class, which doesn't really make sense as it only contains static method anyhow.
So to make the intention clearer, these have now all be reclassified as `private` properties.
---
PHPCSUtils/BackCompat/BCTokens.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/PHPCSUtils/BackCompat/BCTokens.php b/PHPCSUtils/BackCompat/BCTokens.php
index 26d2829a..10e5e9a4 100644
--- a/PHPCSUtils/BackCompat/BCTokens.php
+++ b/PHPCSUtils/BackCompat/BCTokens.php
@@ -51,7 +51,7 @@ class BCTokens
*
* @var string[]
*/
- protected static $phpcsCommentTokensTypes = [
+ private static $phpcsCommentTokensTypes = [
'T_PHPCS_ENABLE',
'T_PHPCS_DISABLE',
'T_PHPCS_SET',
@@ -66,7 +66,7 @@ class BCTokens
*
* @var array =>
*/
- protected static $ooScopeTokens = [
+ private static $ooScopeTokens = [
\T_CLASS => \T_CLASS,
\T_ANON_CLASS => \T_ANON_CLASS,
\T_INTERFACE => \T_INTERFACE,
@@ -80,7 +80,7 @@ class BCTokens
*
* @var array =>
*/
- protected static $textStringTokens = [
+ private static $textStringTokens = [
\T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING,
\T_DOUBLE_QUOTED_STRING => \T_DOUBLE_QUOTED_STRING,
\T_INLINE_HTML => \T_INLINE_HTML,
From e4a3ccbc63e267c6feb61dd8213979c4a9d63752 Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Wed, 18 Mar 2020 06:58:27 +0100
Subject: [PATCH 48/79] Various minor tweaks
---
PHPCSUtils/BackCompat/Helper.php | 6 +++---
PHPCSUtils/Utils/NamingConventions.php | 2 +-
PHPCSUtils/Utils/Numbers.php | 2 +-
PHPCSUtils/Utils/TextStrings.php | 2 +-
Tests/BackCompat/Helper/ConfigDataTest.php | 6 +++---
Tests/BackCompat/Helper/GetVersionTest.php | 2 +-
Tests/Fixers/SpacesFixer/SpacesFixerNoSpaceTest.php | 4 ++--
Tests/Fixers/SpacesFixer/TrailingCommentHandlingTest.php | 4 ++--
Tests/Tokens/Collections/ArrowFunctionTokensBCTest.php | 2 +-
9 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/PHPCSUtils/BackCompat/Helper.php b/PHPCSUtils/BackCompat/Helper.php
index aeb0cf5a..4ae37784 100644
--- a/PHPCSUtils/BackCompat/Helper.php
+++ b/PHPCSUtils/BackCompat/Helper.php
@@ -10,8 +10,8 @@
namespace PHPCSUtils\BackCompat;
-use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Exceptions\RuntimeException;
+use PHP_CodeSniffer\Files\File;
/**
* Utility methods to retrieve (configuration) information from PHP_CodeSniffer.
@@ -86,7 +86,7 @@ public static function setConfigData($key, $value, $temp = false, $config = null
return $config->setConfigData($key, $value, $temp);
}
- if (version_compare(self::getVersion(), '3.99.99', '>') === true) {
+ if (\version_compare(self::getVersion(), '3.99.99', '>') === true) {
throw new RuntimeException('Passing the $config parameter is required in PHPCS 4.x');
}
@@ -183,7 +183,7 @@ public static function getEncoding(File $phpcsFile = null)
if (isset($default) === false) {
$default = 'utf-8';
- if (version_compare(self::getVersion(), '2.99.99', '<=') === true) {
+ if (\version_compare(self::getVersion(), '2.99.99', '<=') === true) {
// In PHPCS 2.x, the default encoding is `iso-8859-1`.
$default = 'iso-8859-1';
}
diff --git a/PHPCSUtils/Utils/NamingConventions.php b/PHPCSUtils/Utils/NamingConventions.php
index 385ba21c..4eefa224 100644
--- a/PHPCSUtils/Utils/NamingConventions.php
+++ b/PHPCSUtils/Utils/NamingConventions.php
@@ -66,7 +66,7 @@ class NamingConventions
*/
public static function isValidIdentifierName($name)
{
- if (is_string($name) === false || $name === '' || \strpos($name, ' ') !== false) {
+ if (\is_string($name) === false || $name === '' || \strpos($name, ' ') !== false) {
return false;
}
diff --git a/PHPCSUtils/Utils/Numbers.php b/PHPCSUtils/Utils/Numbers.php
index c05788f9..662e0072 100644
--- a/PHPCSUtils/Utils/Numbers.php
+++ b/PHPCSUtils/Utils/Numbers.php
@@ -254,7 +254,7 @@ public static function getCompleteNumber(File $phpcsFile, $stackPtr)
while (isset($tokens[++$next], self::$numericLiteralAcceptedTokens[$tokens[$next]['code']]) === true) {
if ($tokens[$next]['code'] === \T_STRING
- && \preg_match($regex, $tokens[$next]['content'], $matches) !== 1
+ && \preg_match($regex, $tokens[$next]['content']) !== 1
) {
break;
}
diff --git a/PHPCSUtils/Utils/TextStrings.php b/PHPCSUtils/Utils/TextStrings.php
index 3c314e36..87c5b45b 100644
--- a/PHPCSUtils/Utils/TextStrings.php
+++ b/PHPCSUtils/Utils/TextStrings.php
@@ -93,7 +93,7 @@ public static function getCompleteTextString(File $phpcsFile, $stackPtr, $stripQ
if ($stripNewline === true) {
// Heredoc/nowdoc: strip the new line at the end of the string to emulate how PHP sees the string.
- $string = rtrim($string, "\r\n");
+ $string = \rtrim($string, "\r\n");
}
if ($stripQuotes === true) {
diff --git a/Tests/BackCompat/Helper/ConfigDataTest.php b/Tests/BackCompat/Helper/ConfigDataTest.php
index 9cd2a74e..5677d454 100644
--- a/Tests/BackCompat/Helper/ConfigDataTest.php
+++ b/Tests/BackCompat/Helper/ConfigDataTest.php
@@ -34,7 +34,7 @@ class ConfigDataTest extends TestCase
*/
public function testConfigData34()
{
- if (version_compare(Helper::getVersion(), '2.99.99', '<=') === true) {
+ if (\version_compare(Helper::getVersion(), '2.99.99', '<=') === true) {
$this->markTestSkipped('Test only applicable to PHPCS > 2.x');
}
@@ -59,7 +59,7 @@ public function testConfigData34()
*/
public function testConfigDataPHPCS23()
{
- if (version_compare(Helper::getVersion(), '3.99.99', '>') === true) {
+ if (\version_compare(Helper::getVersion(), '3.99.99', '>') === true) {
$this->markTestSkipped('Test only applicable to PHPCS < 4.x');
}
@@ -83,7 +83,7 @@ public function testConfigDataPHPCS23()
*/
public function testConfigDataPHPCS4Exception()
{
- if (version_compare(Helper::getVersion(), '3.99.99', '<=') === true) {
+ if (\version_compare(Helper::getVersion(), '3.99.99', '<=') === true) {
$this->markTestSkipped('Test only applicable to PHPCS 4.x');
}
diff --git a/Tests/BackCompat/Helper/GetVersionTest.php b/Tests/BackCompat/Helper/GetVersionTest.php
index c00df963..da6862aa 100644
--- a/Tests/BackCompat/Helper/GetVersionTest.php
+++ b/Tests/BackCompat/Helper/GetVersionTest.php
@@ -32,7 +32,7 @@ class GetVersionTest extends TestCase
*
* @var string
*/
- const DEVMASTER = '3.5.4';
+ const DEVMASTER = '3.5.6';
/**
* Test the method.
diff --git a/Tests/Fixers/SpacesFixer/SpacesFixerNoSpaceTest.php b/Tests/Fixers/SpacesFixer/SpacesFixerNoSpaceTest.php
index 47e41207..1fd02d75 100644
--- a/Tests/Fixers/SpacesFixer/SpacesFixerNoSpaceTest.php
+++ b/Tests/Fixers/SpacesFixer/SpacesFixerNoSpaceTest.php
@@ -53,7 +53,7 @@ class SpacesFixerNoSpaceTest extends UtilityMethodTestCase
*
* @var string
*/
- const CODE = 'PHPCSUtils.SpacerFixer.Test.Found';
+ const CODE = 'PHPCSUtils.SpacesFixer.Test.Found';
/**
* Dummy metric name to use for the test.
@@ -88,7 +88,7 @@ class SpacesFixerNoSpaceTest extends UtilityMethodTestCase
*
* @var array
*/
- protected static $selectedSniff = ['PHPCSUtils.SpacerFixer.Test'];
+ protected static $selectedSniff = ['PHPCSUtils.SpacesFixer.Test'];
/**
* Initialize PHPCS & tokenize the test case file.
diff --git a/Tests/Fixers/SpacesFixer/TrailingCommentHandlingTest.php b/Tests/Fixers/SpacesFixer/TrailingCommentHandlingTest.php
index 12329cf3..a04858b3 100644
--- a/Tests/Fixers/SpacesFixer/TrailingCommentHandlingTest.php
+++ b/Tests/Fixers/SpacesFixer/TrailingCommentHandlingTest.php
@@ -53,7 +53,7 @@ class TrailingCommentHandlingTest extends UtilityMethodTestCase
*
* @var string
*/
- const CODE = 'PHPCSUtils.SpacerFixer.Test.Found';
+ const CODE = 'PHPCSUtils.SpacesFixer.Test.Found';
/**
* Dummy metric name to use for the test.
@@ -67,7 +67,7 @@ class TrailingCommentHandlingTest extends UtilityMethodTestCase
*
* @var array
*/
- protected static $selectedSniff = ['PHPCSUtils.SpacerFixer.Test'];
+ protected static $selectedSniff = ['PHPCSUtils.SpacesFixer.Test'];
/**
* Test that violations are correctly reported.
diff --git a/Tests/Tokens/Collections/ArrowFunctionTokensBCTest.php b/Tests/Tokens/Collections/ArrowFunctionTokensBCTest.php
index c0c0767c..2d45f09c 100644
--- a/Tests/Tokens/Collections/ArrowFunctionTokensBCTest.php
+++ b/Tests/Tokens/Collections/ArrowFunctionTokensBCTest.php
@@ -44,6 +44,6 @@ public function testArrowFunctionTokensBC()
$expected[\T_FN] = \T_FN;
}
- $this->assertSame($expected, Collections::ArrowFunctionTokensBC());
+ $this->assertSame($expected, Collections::arrowFunctionTokensBC());
}
}
From a983cd659a150371989a72b9e734f458b8af5095 Mon Sep 17 00:00:00 2001
From: jrfnl
Date: Sat, 30 May 2020 20:25:05 +0200
Subject: [PATCH 49/79] Docs website: convert to using Jekyll
The initial homepage was set up in HTML as it (temporarily) had to be hosted elsewhere.
The intention was always to use GH Pages for hosting the site and to use Jekyll to generate the homepage content based on the `README` of the project.
To allow for that to happen, some changes were needed which are contained in this PR.
1. Add a `Gemfile` pointing enabling the GH Pages gem, including enabling a new GH flavoured markdown interpreter.
2. Add a `_config.yml` file.
- Enable the new GH flavoured markdown interpreter.
- Set the theme.
- Enable a variety of plugins.
- Add the `repository` and `github` keys to allow for local testing.
- Add a range of keys with information for the theme and SEO plugin.
- Explicitly exclude a number of files from being included in the generated site.
3. The initial homepage was already based on the GH Pages Jekyll `minimal` theme.
To allow the site to take full advantage of that, most copied in files from the theme can be removed as they will automatically be added now, as well as updated when needed.
4. A `.gitignore` file is put in place to ignore the typical Gem related files as well as the Jekyll output in `_site` from being committed.
5. A `.nojekyll` file is put in place in the `phpdoc` subdirectory to prevent Jekyll trying to parse the PHPDocumentor generated documentation.
6. The CSS customizations have been moved from the (theme generated) `style.css` file to `style.scss`.
This makes it crystal clear what the customizations are and what comes from the theme.
The Jekyll build will combine the CSS into one `style.css` file for the generated site.
7. The theme `_default.html` layout has been copied in from the theme and minimal adjustments have been made to:
- Overrule the syntax highlighting styles by setting a separate stylesheet for those.
- Add the "Read the docs" button in the left column.
- Add the "Install using Composer" section in the left column.
- Add the Twitter share button in the left column.
8. Add a `robots.txt` file pointing to the plugin generated XML sitemap fo search engines.
9. Replace the `index.html` file with an `index.md` file which contains the same basic contents as the `README.md` file.
Includes making a few small adjustments to the `README.md` file.
This should now allow for doing a plain copy & paste of the README content into the `docs/index.md` just before each release to update the homepage to the latest content.
Note: there is a "readme to index" plugin available, but that does not allow for the readme and the docs being in different directories.
A [feature request](https://github.com/benbalter/jekyll-readme-index/issues/19) to allow for that has been opened.
Once this is merged, to update the website means:
1. Running PHPDocumentor.
2. Copy & pasting the content from the `README.md` file to `docs/index.md` (and double-checking the few remaining differences are intact).
3. Once those changes have been merged and `develop` has been merged into `master`, the website should be updated.
The website can be viewed locally by running:
```bash
bundle update
bundle exec jekyll serve
```
and then visiting http://localhost:4000/ to see the result.
---
README.md | 10 +-
docs/.gitignore | 4 +
docs/Gemfile | 5 +
docs/_config.yml | 36 ++
docs/_layouts/default.html | 79 ++++
docs/assets/css/style.css | 322 -----------------
docs/assets/css/style.scss | 125 +++++++
.../fonts/Noto-Sans-700/Noto-Sans-700.eot | Bin 16716 -> 0 bytes
.../fonts/Noto-Sans-700/Noto-Sans-700.svg | 336 -----------------
.../fonts/Noto-Sans-700/Noto-Sans-700.ttf | Bin 29704 -> 0 bytes
.../fonts/Noto-Sans-700/Noto-Sans-700.woff | Bin 12632 -> 0 bytes
.../fonts/Noto-Sans-700/Noto-Sans-700.woff2 | Bin 9724 -> 0 bytes
.../Noto-Sans-700italic.eot | Bin 16849 -> 0 bytes
.../Noto-Sans-700italic.svg | 334 -----------------
.../Noto-Sans-700italic.ttf | Bin 28932 -> 0 bytes
.../Noto-Sans-700italic.woff | Bin 12612 -> 0 bytes
.../Noto-Sans-700italic.woff2 | Bin 9612 -> 0 bytes
.../Noto-Sans-italic/Noto-Sans-italic.eot | Bin 15864 -> 0 bytes
.../Noto-Sans-italic/Noto-Sans-italic.svg | 337 ------------------
.../Noto-Sans-italic/Noto-Sans-italic.ttf | Bin 26644 -> 0 bytes
.../Noto-Sans-italic/Noto-Sans-italic.woff | Bin 12536 -> 0 bytes
.../Noto-Sans-italic/Noto-Sans-italic.woff2 | Bin 9572 -> 0 bytes
.../Noto-Sans-regular/Noto-Sans-regular.eot | Bin 16639 -> 0 bytes
.../Noto-Sans-regular/Noto-Sans-regular.svg | 335 -----------------
.../Noto-Sans-regular/Noto-Sans-regular.ttf | Bin 29288 -> 0 bytes
.../Noto-Sans-regular/Noto-Sans-regular.woff | Bin 12840 -> 0 bytes
.../Noto-Sans-regular/Noto-Sans-regular.woff2 | Bin 9932 -> 0 bytes
docs/assets/js/scale.fix.js | 27 --
docs/index.html | 204 -----------
docs/index.md | 146 ++++++++
docs/phpdoc/.nojekyll | 0
docs/robots.txt | 5 +
32 files changed, 408 insertions(+), 1897 deletions(-)
create mode 100644 docs/.gitignore
create mode 100644 docs/Gemfile
create mode 100644 docs/_config.yml
create mode 100644 docs/_layouts/default.html
delete mode 100644 docs/assets/css/style.css
create mode 100644 docs/assets/css/style.scss
delete mode 100644 docs/assets/fonts/Noto-Sans-700/Noto-Sans-700.eot
delete mode 100644 docs/assets/fonts/Noto-Sans-700/Noto-Sans-700.svg
delete mode 100644 docs/assets/fonts/Noto-Sans-700/Noto-Sans-700.ttf
delete mode 100644 docs/assets/fonts/Noto-Sans-700/Noto-Sans-700.woff
delete mode 100644 docs/assets/fonts/Noto-Sans-700/Noto-Sans-700.woff2
delete mode 100644 docs/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot
delete mode 100644 docs/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.svg
delete mode 100644 docs/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.ttf
delete mode 100644 docs/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff
delete mode 100644 docs/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff2
delete mode 100644 docs/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.eot
delete mode 100644 docs/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.svg
delete mode 100644 docs/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.ttf
delete mode 100644 docs/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.woff
delete mode 100644 docs/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.woff2
delete mode 100644 docs/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.eot
delete mode 100644 docs/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.svg
delete mode 100644 docs/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.ttf
delete mode 100644 docs/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.woff
delete mode 100644 docs/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.woff2
delete mode 100644 docs/assets/js/scale.fix.js
delete mode 100644 docs/index.html
create mode 100644 docs/index.md
create mode 100644 docs/phpdoc/.nojekyll
create mode 100644 docs/robots.txt
diff --git a/README.md b/README.md
index dec13a48..7a55f8dc 100644
--- a/README.md
+++ b/README.md
@@ -31,10 +31,12 @@ PHPCSUtils: A suite of utility functions for use with PHP_CodeSniffer
Features
-------------------------------------------
-This is a set of utilities to aid developers of sniffs for [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer).
+[PHPCSUtils](https://github.com/PHPCSStandards/PHPCSUtils) is a set of utilities to aid developers of sniffs for [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer).
This package offers the following features:
+
+
### Use the latest version of PHP_CodeSniffer native utility functions.
Normally to use the latest version of PHP_CodeSniffer native utility functions, you would have to raise the minimum requirements of your external PHPCS standard.
@@ -71,6 +73,7 @@ A `PHPCS23Utils` standard which allows sniffs to work in both PHPCS 2.x and 3.x,
To see detailed information about all the available abstract sniffs, utility functions and PHPCS helper functions, have a read through the [extensive documentation](https://phpcsutils.com/).
+
Minimum Requirements
-------------------------------------------
@@ -216,6 +219,8 @@ Once that's done, you will need to make a small tweak to your own dev environmen
Frequently Asked Questions
-------
+
+
#### Q: How does this all work without an external standard needing to register an autoloader?
A: As PHPCSUtils is registered with PHPCS as an external standard and PHPCSUtils complies with the naming requirements of PHPCS, the PHPCS native autoloader will automatically take care of loading the classes you use from PHPCSUtils.
@@ -232,6 +237,7 @@ A: The backfill for PHP 7.4 numeric literals with underscores in PHP_CodeSniffer
The backfill was fixed in PHP_CodeSniffer 3.5.4.
+
Contributing
-------
@@ -241,4 +247,4 @@ If you are unsure whether the changes you are proposing would be welcome, please
License
-------
-This code is released under the GNU Lesser General Public License (LGPLv3). For more information, visit http://www.gnu.org/copyleft/lesser.html
+This code is released under the [GNU Lesser General Public License (LGPLv3)](http://www.gnu.org/copyleft/lesser.html).
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 00000000..a5a45a8b
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,4 @@
+_site/*
+Gemfile.lock
+.sass-cache
+*.gem
diff --git a/docs/Gemfile b/docs/Gemfile
new file mode 100644
index 00000000..b78190ab
--- /dev/null
+++ b/docs/Gemfile
@@ -0,0 +1,5 @@
+source 'https://rubygems.org'
+
+gem "github-pages", group: :jekyll_plugins do
+ gem 'jekyll-commonmark-ghpages'
+end
\ No newline at end of file
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 00000000..285eb8e2
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1,36 @@
+baseurl: /
+highlighter: rouge
+markdown: CommonMarkGhPages
+encoding: UTF-8
+theme: jekyll-theme-minimal
+repository: "PHPCSStandards/PHPCSUtils"
+github: [metadata]
+url: "https://phpcsutils.com"
+
+plugins:
+ - jekyll-github-metadata
+ - jemoji
+ - jekyll-mentions
+ - jekyll-seo-tag
+ - jekyll-sitemap
+
+phpcsutils:
+ packagist: phpcsstandards/phpcsutils
+
+# Theme info.
+title: PHPCSUtils
+description: "A suite of utility functions for use with PHP_CodeSniffer."
+logo:
+show_downloads: false
+google_analytics:
+
+# SEO info.
+tagline: "PHPCSUtils: A suite of utility functions for use with PHP_CodeSniffer."
+twitter:
+ username: jrf_nl
+ card: summary
+ hashtags: PHPCSUtils
+author:
+ twitter: jrf_nl
+
+exclude: ['CNAME', '.gitignore', 'Gemfile', '*.bak', '*.orig']
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html
new file mode 100644
index 00000000..e9d910d8
--- /dev/null
+++ b/docs/_layouts/default.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+{% seo %}
+
+
+
+
+
+
+
+ {{ site.title | default: site.github.repository_name }}
+
+ {% if site.logo %}
+
+ {% endif %}
+
+ {{ site.description | default: site.github.project_tagline }}
+
+ {% if site.github.is_project_page %}
+ Visit the Project on GitHub {{ site.github.repository_nwo }}
+ {% endif %}
+
+
+
+
+
+ {% if site.show_downloads %}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
+ {% if site.google_analytics %}
+
+ {% endif %}
+
+
+
+
diff --git a/docs/assets/css/style.css b/docs/assets/css/style.css
deleted file mode 100644
index bbda1925..00000000
--- a/docs/assets/css/style.css
+++ /dev/null
@@ -1,322 +0,0 @@
-@font-face {
- font-family:'Noto Sans';
- font-weight:400;
- font-style:normal;
- src:url("../fonts/Noto-Sans-regular/Noto-Sans-regular.eot");
- src:url("../fonts/Noto-Sans-regular/Noto-Sans-regular.eot?#iefix") format("embedded-opentype"),local("Noto Sans"),local("Noto-Sans-regular"),url("../fonts/Noto-Sans-regular/Noto-Sans-regular.woff2") format("woff2"),url("../fonts/Noto-Sans-regular/Noto-Sans-regular.woff") format("woff"),url("../fonts/Noto-Sans-regular/Noto-Sans-regular.ttf") format("truetype"),url("../fonts/Noto-Sans-regular/Noto-Sans-regular.svg#NotoSans") format("svg");
-}
-@font-face {
- font-family:'Noto Sans';
- font-weight:700;
- font-style:normal;
- src:url("../fonts/Noto-Sans-700/Noto-Sans-700.eot");
- src:url("../fonts/Noto-Sans-700/Noto-Sans-700.eot?#iefix") format("embedded-opentype"),local("Noto Sans Bold"),local("Noto-Sans-700"),url("../fonts/Noto-Sans-700/Noto-Sans-700.woff2") format("woff2"),url("../fonts/Noto-Sans-700/Noto-Sans-700.woff") format("woff"),url("../fonts/Noto-Sans-700/Noto-Sans-700.ttf") format("truetype"),url("../fonts/Noto-Sans-700/Noto-Sans-700.svg#NotoSans") format("svg");
-}
-@font-face {
- font-family:'Noto Sans';
- font-weight:400;
- font-style:italic;
- src:url("../fonts/Noto-Sans-italic/Noto-Sans-italic.eot");
- src:url("../fonts/Noto-Sans-italic/Noto-Sans-italic.eot?#iefix") format("embedded-opentype"),local("Noto Sans Italic"),local("Noto-Sans-italic"),url("../fonts/Noto-Sans-italic/Noto-Sans-italic.woff2") format("woff2"),url("../fonts/Noto-Sans-italic/Noto-Sans-italic.woff") format("woff"),url("../fonts/Noto-Sans-italic/Noto-Sans-italic.ttf") format("truetype"),url("../fonts/Noto-Sans-italic/Noto-Sans-italic.svg#NotoSans") format("svg");
-}
-@font-face {
- font-family:'Noto Sans';
- font-weight:700;
- font-style:italic;
- src:url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot");
- src:url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot?#iefix") format("embedded-opentype"),local("Noto Sans Bold Italic"),local("Noto-Sans-700italic"),url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff2") format("woff2"),url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff") format("woff"),url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.ttf") format("truetype"),url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.svg#NotoSans") format("svg");
-}
-.highlight table td {
- padding:5px;
-}
-.highlight table pre {
- margin:0;
-}
-
-body {
- background-color:#fff;
- padding:50px;
- font:14px/1.5 "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
- color:#727272;
- font-weight:400;
-}
-h1, h2, h3, h4, h5, h6 {
- color:#222;
- margin:0 0 20px;
-}
-p, ul, ol, table, pre, dl {
- margin:0 0 15px;
-}
-h1, h2, h3 {
- line-height:1.1;
-}
-h1 {
- font-size:28px;
- margin-top: 1.5em;
-}
-header h1, h1#header-1 {
- margin-top: 0em;
-}
-h2 {
- color:#393939;
- font-size: 18px
-}
-h3 {
- font-size:
- 14px;
- margin-top: 1.5em;
-}
-h3, h4, h5, h6 {
- color:#494949;
-}
-a {
- color:#267CB9;
- text-decoration:none;
-}
-a:hover, a:focus {
- color:#069;
- font-weight:bold;
-}
-a small {
- font-size:11px;
- color:#777;
- margin-top:-0.3em;
- display:block;
-}
-a:hover small {
- color:#777;
-}
-.wrapper{
- width:100%;
- margin:0 auto;
-}
-blockquote{
- border-left:2px solid #e5e5e5;
- border-right:2px solid #e5e5e5;
- margin:0 20px 0 10px;
- padding:0 10px 0 20px;
- font-style:italic;
-}
-blockquote p {
- margin:2px 0 8px;
-}
-code, pre {
- font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, Consolas, Liberation Mono, DejaVu Sans Mono, Courier New, monospace;
- color:#333;
-}
-span code {
- background:#f8f8f8;
- display:inline-block;
-}
-pre {
- padding:8px 15px;
- background:#f8f8f8;
- border-radius:5px;
- border:1px solid #e5e5e5;
- overflow-x:auto;
-}
-table{
- width:100%;
- border-collapse:collapse;
-}
-th, td {
- text-align:left;
- padding:5px 10px;
- border-bottom:1px solid #e5e5e5;
-}
-dt {
- color:#444;
- font-weight:700;
-}
-dd p {
- margin:2px 0 8px;
-}
-th {
- color:#444;
-}
-img {
- max-width:100%;
-}
-header {
- width:28%;
- float:left;
- position:fixed;
- -webkit-font-smoothing:subpixel-antialiased;
-}
-
-.install pre.highlight {
- padding: 6px 10px;
- text-align: center;
- font-size: 89%;
-}
-.docs {
- box-shadow:inset 0px 1px 0px 0px #97c4fe;
- background:linear-gradient(to bottom, #3d94f6 5%, #1e62d0 100%);
- background-color:#267CB9;
- border-radius:6px;
- border:1px solid #337fed;
- display:inline-block;
- cursor:pointer;
- color:#ffffff;
- font-family:Arial;
- font-size:15px;
- font-weight:bold;
- padding:8px 24px;
- text-decoration:none;
- text-shadow:0px 1px 0px #1570cd;
-}
-.docs:hover {
- background:linear-gradient(to bottom, #1e62d0 5%, #3d94f6 100%);
- background-color:#1e62d0;
-}
-.docs:active {
- position:relative;
- top:1px;
-}
-.docs a {
- color: #ffffff;
-}
-
-ul.downloads {
- list-style:none;
- height:40px;
- padding:0;
- background:#f4f4f4;
- border-radius:5px;
- border:1px solid #e0e0e0;
- width:270px;
-}
-.downloads li {
- width:89px;
- float:left;
- border-right:1px solid #e0e0e0;
- height:40px;
-}
-.downloads li:first-child a {
- border-radius:5px 0 0 5px;
-}
-.downloads li:last-child a {
- border-radius:0 5px 5px 0;
-}
-.downloads a {
- line-height:1;
- font-size:11px;
- color:#676767;
- display:block;
- text-align:center;
- padding-top:6px;
- height:34px;
-}
-.downloads a:hover, .downloads a:focus{
- color:#675C5C;
- font-weight:bold;
-}
-.downloads ul a:active {
- background-color:#f0f0f0;
-}
-strong {
- color:#222;
- font-weight:700;
-}
-.downloads li+li+li {
- border-right:none;
- width:89px;
-}
-.downloads a strong {
- font-size:14px;
- display:block;
- color:#222;
-}
-#faq span.q, #faq span.a {
- color:#267CB9;
- font-weight: bold;
-}
-
-.twitter-share-button {
- font-size: 70%;
- margin: 0;
- padding-top: 1em;
-}
-
-section {
- width:60%;
- float:right;
- padding-bottom:50px;
- padding-right: 50px;
-}
-small {
- font-size:11px;
-}
-hr {
- border:0;
- background:#e5e5e5;
- height:1px;
- margin:0 0 20px;
-}
-footer {
- width:28%;
- float:left;
- position:fixed;
- bottom:20px;
- -webkit-font-smoothing:subpixel-antialiased;
-}
-@media print, screen and (max-width: 960px) {
- div.wrapper {
- width:auto;
- margin:0;
- }
- header, section, footer {
- float:none;
- position:static;
- width:auto;
- }
- header {
- padding-right:320px;
- }
- section {
- border:1px solid #e5e5e5;
- border-width:1px 0;
- padding:20px 0;
- margin:0 0 20px;
- }
- header a small {
- display:inline;
- }
- header ul {
- position:absolute;
- right:50px;
- top:52px;
- }
-}
-@media print, screen and (max-width: 720px) {
- body {
- word-wrap:break-word;
- }
- header {
- padding:0;
- }
- header ul, header p.view {
- position:static;
- }
- pre,code {
- word-wrap:normal;
- }
-}
-@media print, screen and (max-width: 480px) {
- body {
- padding:15px;
- }
- .downloads {
- width:99%;
- }
- .downloads li, .downloads li+li+li {
- width:33%;
- }
-}
-@media print {
- body {
- padding:0.4in;
- font-size:12pt;
- color:#444;
- }
-}
diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss
new file mode 100644
index 00000000..acf04ec4
--- /dev/null
+++ b/docs/assets/css/style.scss
@@ -0,0 +1,125 @@
+---
+---
+
+@import "{{ site.theme }}";
+
+p, ul, ol, table, pre, #feature-list {
+ margin:0 0 15px;
+}
+
+h1, h2 {
+ margin-top: 1.5em;
+}
+header h1, h2#features {
+ margin-top: 0em;
+}
+
+h2 {
+ font-size:28px;
+}
+h3 {
+ font-size: 18px;
+}
+h4 {
+ font-size: 14px;
+ margin-top: 1.5em;
+}
+
+blockquote{
+ border-left:2px solid #e5e5e5;
+ border-right:2px solid #e5e5e5;
+ margin:5px 20px 0 10px;
+ padding:0 10px 0 20px;
+}
+blockquote p {
+ margin:2px 0 8px;
+}
+
+p code {
+ background:#f8f8f8;
+ display:inline-block;
+}
+
+/* First section: feature list */
+#feature-list h3 {
+ font-size: 14px;
+ color: #444;
+ margin: 0;
+ line-height: 1.5;
+}
+
+#feature-list p {
+ margin:2px 0 8px 40px;
+}
+
+/* Packagist install code sample left column. */
+.install pre.highlight {
+ padding: 6px 10px;
+ text-align: center;
+ font-size: 89%;
+}
+
+/* "Read the docs" button. */
+.docs {
+ box-shadow:inset 0px 1px 0px 0px #97c4fe;
+ background:linear-gradient(to bottom, #3d94f6 5%, #1e62d0 100%);
+ background-color:#267CB9;
+ border-radius:6px;
+ border:1px solid #337fed;
+ display:inline-block;
+ cursor:pointer;
+ color:#ffffff;
+ font-family:Arial;
+ font-size:15px;
+ font-weight:bold;
+ padding:8px 24px;
+ text-decoration:none;
+ text-shadow:0px 1px 0px #1570cd;
+}
+.docs:hover {
+ background:linear-gradient(to bottom, #1e62d0 5%, #3d94f6 100%);
+ background-color:#1e62d0;
+}
+.docs:active {
+ position:relative;
+ top:1px;
+}
+.docs a {
+ color: #ffffff;
+}
+
+/* FAQ */
+#faq h4 {
+ font-size: 18px;
+ line-height: 1.1;
+ margin-top: 0;
+}
+
+#faq h4::first-letter, #faq h4 + p::first-letter {
+ color:#267CB9;
+ font-weight: bold;
+}
+
+.twitter-share-button {
+ font-size: 70%;
+ margin: 0;
+ padding-top: 1em;
+}
+
+/* General space usage/layout */
+.wrapper{
+ width:100%;
+}
+
+header {
+ width:28%;
+}
+section {
+ width:60%;
+ padding-right: 50px;
+}
+
+footer {
+ width:28%;
+ bottom:20px;
+}
diff --git a/docs/assets/fonts/Noto-Sans-700/Noto-Sans-700.eot b/docs/assets/fonts/Noto-Sans-700/Noto-Sans-700.eot
deleted file mode 100644
index 03bf93fec2a7341b1a6192ff0d596b05c1765c93..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 16716
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
aO1sP&w|8H~0KVcsPu#OW`
-
-
diff --git a/docs/assets/fonts/Noto-Sans-700/Noto-Sans-700.ttf b/docs/assets/fonts/Noto-Sans-700/Noto-Sans-700.ttf
deleted file mode 100644
index 4599e3ca9af9bf758f3b5d0b79314701f853c371..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 29704
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